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::{
11    CommsDrainMode, CommsDrainPhase, DrainExitReason, RuntimeExecutorAttachmentWitness, dsl,
12};
13use indexmap::IndexSet;
14use meerkat_core::RuntimeEpochId;
15use meerkat_core::agent::CommsRuntime;
16use meerkat_core::image_generation::{
17    ImageOperationApprovalReason, ImageOperationDenialReason, ImageOperationId,
18    ImageOperationPhase, ImageOperationTerminalClass, ImageProviderTerminalObservation,
19    ProviderTextDisposition, SessionModelRoutingStatus, SwitchTurnApprovalReason,
20    SwitchTurnControlResult, SwitchTurnIntent, SwitchTurnRequestId,
21};
22use meerkat_core::lifecycle::WaitRequestId;
23use meerkat_core::lifecycle::core_executor::CoreExecutor;
24use meerkat_core::lifecycle::run_primitive::{ModelId, TurnMetadataOverride};
25use meerkat_core::lifecycle::{InputId, RunId};
26use meerkat_core::lifecycle::{RunBoundaryReceipt, RunId as LifecycleRunId};
27use meerkat_core::ops::OperationId;
28use meerkat_core::ops_lifecycle::OperationLifecycleSnapshot;
29use meerkat_core::types::HandlingMode;
30use meerkat_core::types::SessionId;
31use meerkat_machine_derive::CommandManifest;
32use meerkat_machine_schema::catalog::dsl::meerkat_machine::MeerkatMachineInputVariant;
33use serde::{Deserialize, Serialize};
34
35use crate::AcceptOutcome;
36use crate::identifiers::LogicalRuntimeId;
37use crate::ingress_types::{ContentShape, RequestId, ReservationKey};
38use crate::input::Input;
39use crate::input_state::InputLifecycleState;
40use crate::input_state::InputTerminalOutcome;
41use crate::input_state::StoredInputState;
42use crate::runtime_event::RuntimeEventEnvelope;
43use crate::runtime_state::RuntimeState;
44use crate::traits::{
45    DestroyReport, RecoveryReport, RecycleReport, ResetReport, RetireReport,
46    RuntimeControlPlaneError, RuntimeDriverError,
47};
48
49/// Per-turn LLM hot-swap reconfigure request.
50///
51/// `provider_params` and `auth_binding` carry the canonical Inherit/Set/Clear
52/// tri-state via [`TurnMetadataOverride`]: `None` preserves the durable value,
53/// `Some(Set)` overrides it, and `Some(Clear)` removes it. The illegal
54/// "set and clear" fourth state is structurally unrepresentable.
55#[derive(Debug, Clone, Serialize, PartialEq)]
56#[serde(rename_all = "snake_case")]
57pub struct SessionLlmReconfigureRequest {
58    #[serde(default, skip_serializing_if = "Option::is_none")]
59    pub model: Option<String>,
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub provider: Option<String>,
62    /// Exact configured server route for a self-hosted model. Provider/model
63    /// alone cannot distinguish two local servers exposing the same model id.
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub self_hosted_server_id: Option<String>,
66    #[serde(default, skip_serializing_if = "Option::is_none")]
67    pub provider_params: Option<
68        TurnMetadataOverride<meerkat_core::lifecycle::run_primitive::ProviderParamsOverride>,
69    >,
70    /// Optional realm-scoped connection override resolved through the tri-state:
71    /// `Some(Set)` swaps the binding, `Some(Clear)` removes it, `None` preserves
72    /// the session's existing `SessionLlmIdentity.auth_binding`.
73    #[serde(default, skip_serializing_if = "Option::is_none")]
74    pub auth_binding: Option<TurnMetadataOverride<meerkat_core::AuthBindingRef>>,
75}
76
77#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
78#[serde(rename_all = "snake_case")]
79pub enum SessionLlmCapabilitySurfaceStatus {
80    Resolved,
81    #[default]
82    Unresolved,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86#[serde(rename_all = "snake_case")]
87pub struct SessionLlmCapabilitySurface {
88    pub supports_temperature: bool,
89    pub supports_thinking: bool,
90    pub supports_reasoning: bool,
91    pub inline_video: bool,
92    pub vision: bool,
93    #[serde(default)]
94    pub image_input: bool,
95    pub image_tool_results: bool,
96    pub supports_web_search: bool,
97    #[serde(default)]
98    pub image_generation: bool,
99    /// Whether the resolved model exposes a realtime bidirectional streaming
100    /// transport. Drives capability-based auto attach/detach in
101    /// `reconfigure_live_topology` and `apply_capability_driven_realtime_transport`.
102    #[serde(default)]
103    pub realtime: bool,
104    #[serde(default, skip_serializing_if = "Option::is_none")]
105    pub call_timeout_secs: Option<u64>,
106}
107
108impl SessionLlmCapabilitySurface {
109    #[must_use]
110    pub fn to_wire_resolved(&self) -> meerkat_contracts::WireResolvedModelCapabilities {
111        meerkat_contracts::WireResolvedModelCapabilities {
112            vision: self.vision,
113            image_input: self.image_input,
114            image_tool_results: self.image_tool_results,
115            inline_video: self.inline_video,
116            realtime: self.realtime,
117            web_search: self.supports_web_search,
118            image_generation: self.image_generation,
119        }
120    }
121}
122
123#[derive(Debug, Clone, PartialEq, Eq)]
124pub struct SessionLlmCapabilityDelta {
125    pub previous: Option<SessionLlmCapabilitySurface>,
126    pub current: Option<SessionLlmCapabilitySurface>,
127    pub changed: bool,
128}
129
130#[derive(Debug, Clone, PartialEq, Eq)]
131pub struct SessionToolVisibilityDelta {
132    pub previous_capability_base_filter: meerkat_core::ToolFilter,
133    pub current_capability_base_filter: meerkat_core::ToolFilter,
134    pub committed_visible_set_changed: bool,
135    pub revision_bumped: bool,
136}
137
138#[derive(Debug, Clone, PartialEq)]
139pub struct SessionLlmReconfigureReport {
140    pub previous_identity: meerkat_core::SessionLlmIdentity,
141    pub new_identity: meerkat_core::SessionLlmIdentity,
142    pub capability_delta: SessionLlmCapabilityDelta,
143    pub tool_visibility_delta: SessionToolVisibilityDelta,
144    pub rollback_occurred: bool,
145}
146
147#[derive(Debug, Clone)]
148pub struct HydratedSessionLlmState {
149    pub current_identity: meerkat_core::SessionLlmIdentity,
150    pub current_visibility_state: meerkat_core::SessionToolVisibilityState,
151    pub current_capability_surface: Option<SessionLlmCapabilitySurface>,
152    pub capability_surface_status: SessionLlmCapabilitySurfaceStatus,
153    pub base_tool_names: std::collections::BTreeSet<meerkat_core::ToolName>,
154}
155
156#[derive(Debug, Clone)]
157pub struct ResolvedSessionLlmReconfigure {
158    pub target_identity: meerkat_core::SessionLlmIdentity,
159    pub target_capability_surface: SessionLlmCapabilitySurface,
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ModelRoutingApprovalDisposition {
164    NotRequired,
165    Approved,
166    DeniedByUser,
167    RequiredButUnavailable,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq)]
171pub struct ModelRoutingRealtimePolicy {
172    pub target_realtime_capable: bool,
173    pub allow_realtime_detach: bool,
174}
175
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct SwitchTurnRequest {
178    pub request_id: SwitchTurnRequestId,
179    pub intent: SwitchTurnIntent,
180    pub target_realtime: ModelRoutingRealtimePolicy,
181    pub approval: ModelRoutingApprovalDisposition,
182    pub approval_reason: Option<SwitchTurnApprovalReason>,
183}
184
185#[derive(Debug, Clone, PartialEq, Eq)]
186pub struct ImageOperationRoutingRequest {
187    pub operation_id: ImageOperationId,
188    pub target_model: ModelId,
189    pub target_realtime: ModelRoutingRealtimePolicy,
190    pub approval: ModelRoutingApprovalDisposition,
191    pub approval_reason: Option<ImageOperationApprovalReason>,
192    pub requires_scoped_override: bool,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq)]
196pub enum ImageOperationRoutingResult {
197    Accepted {
198        operation_id: ImageOperationId,
199        phase: ImageOperationPhase,
200    },
201    Denied {
202        operation_id: ImageOperationId,
203        reason: ImageOperationDenialReason,
204    },
205}
206
207#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
208#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
209pub trait SessionLlmReconfigureHost: Send + Sync {
210    /// Acquire the stable session-service boundary that must enclose the
211    /// complete hydrate -> generated-machine commit -> live mutation ->
212    /// persistence transaction. Runtime-loop callers already hold the same
213    /// boundary; direct control-plane callers acquire it through this hook.
214    async fn acquire_turn_finalization_boundary(
215        &self,
216        _session_id: &SessionId,
217    ) -> Result<
218        Box<dyn meerkat_core::lifecycle::CoreExecutorTurnFinalizationGuard>,
219        RuntimeDriverError,
220    > {
221        Ok(Box::new(()))
222    }
223
224    async fn hydrate_session_llm_state(
225        &self,
226        session_id: &SessionId,
227    ) -> Result<HydratedSessionLlmState, RuntimeDriverError>;
228
229    async fn resolve_target_session_llm_identity(
230        &self,
231        request: &SessionLlmReconfigureRequest,
232        current_identity: &meerkat_core::SessionLlmIdentity,
233    ) -> Result<ResolvedSessionLlmReconfigure, RuntimeDriverError>;
234
235    /// Caller must hold the turn-finalization boundary.
236    async fn apply_live_session_llm_identity(
237        &self,
238        session_id: &SessionId,
239        identity: &meerkat_core::SessionLlmIdentity,
240    ) -> Result<(), RuntimeDriverError>;
241
242    /// Caller must hold the turn-finalization boundary.
243    async fn apply_live_session_tool_visibility_state(
244        &self,
245        session_id: &SessionId,
246        visibility_state: Option<meerkat_core::SessionToolVisibilityState>,
247    ) -> Result<(), RuntimeDriverError>;
248
249    /// Caller must hold the turn-finalization boundary.
250    async fn persist_live_session(&self, session_id: &SessionId) -> Result<(), RuntimeDriverError>;
251
252    /// Caller must hold the turn-finalization boundary.
253    async fn discard_live_session(&self, session_id: &SessionId) -> Result<(), RuntimeDriverError>;
254}
255
256#[derive(Debug, thiserror::Error)]
257pub(crate) enum MeerkatMachineCommandError {
258    #[error(transparent)]
259    Driver(#[from] RuntimeDriverError),
260    #[error(transparent)]
261    Control(#[from] RuntimeControlPlaneError),
262}
263
264/// Unified internal Meerkat machine command surface.
265///
266/// This replaces the old per-domain dispatch split (session, drain,
267/// drain-local, control, ingress) while keeping the public helper
268/// methods and external runtime/machine surface unchanged.
269#[derive(CommandManifest)]
270#[allow(clippy::large_enum_variant)]
271pub(crate) enum MeerkatMachineCommand {
272    RegisterSession {
273        session_id: SessionId,
274    },
275    // Retained for generated manifest/parity coverage; production call sites
276    // prefer typed unregister helpers.
277    #[cfg_attr(not(test), allow(dead_code))]
278    UnregisterSession {
279        session_id: SessionId,
280    },
281    EnsureSessionWithExecutor {
282        session_id: SessionId,
283        executor: Box<dyn CoreExecutor>,
284    },
285    SetSilentIntents {
286        session_id: SessionId,
287        intents: Vec<String>,
288    },
289    CancelAfterBoundary {
290        session_id: SessionId,
291    },
292    StopRuntimeExecutor {
293        session_id: SessionId,
294        reason: String,
295    },
296    #[cfg_attr(not(test), allow(dead_code))]
297    ContainsSession {
298        session_id: SessionId,
299    },
300    SessionHasExecutor {
301        session_id: SessionId,
302    },
303    SessionHasComms {
304        session_id: SessionId,
305    },
306    OpsLifecycleRegistry {
307        session_id: SessionId,
308    },
309    // Retained for generated composition contracts; production binding requests
310    // normally enter through typed composition helpers.
311    #[cfg_attr(not(test), allow(dead_code))]
312    PrepareBindings {
313        session_id: SessionId,
314    },
315    // Local bootstrap is a shell mechanic, kept in the command catalog so the
316    // generated classifier can spell that boundary explicitly.
317    #[cfg_attr(not(test), allow(dead_code))]
318    PrepareLocalSessionBindings {
319        session_id: SessionId,
320    },
321    InputState {
322        session_id: SessionId,
323        input_id: InputId,
324    },
325    // Read-only reconciliation lookup: resolve the machine-owned
326    // idempotency-key binding to its input and return that input's stored
327    // state. Same authority read as `InputState`; the key resolution is a
328    // mechanical mirror of the generated admission map (never a dedup
329    // decision — `ResolveAdmissionIdempotency` on the accept path stays the
330    // only mutator).
331    InputStateByIdempotencyKey {
332        session_id: SessionId,
333        idempotency_key: String,
334    },
335    // Restart-first-class terminal-status read: same machine-owned authority
336    // read as `InputState`, but when the session is not registered a
337    // persistent machine answers from the durably committed input-state
338    // witnesses in the RuntimeStore instead of failing NotReady.
339    InteractionTerminalStatus {
340        session_id: SessionId,
341        selector: crate::terminal_status::InteractionSelector,
342    },
343    // Run-id-keyed terminal-status read over the same witnesses: the durable
344    // run-terminal truth is the set of inputs whose `last_run_id` references
345    // the run, evaluated by the canonical pure evaluator.
346    RunTerminalStatus {
347        session_id: SessionId,
348        run_id: RunId,
349    },
350    ListActiveInputs {
351        session_id: SessionId,
352    },
353    ReconfigureSessionLlmIdentity {
354        session_id: SessionId,
355        previous_identity: Box<meerkat_core::SessionLlmIdentity>,
356        previous_visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
357        previous_capability_surface: Option<SessionLlmCapabilitySurface>,
358        previous_capability_surface_status: SessionLlmCapabilitySurfaceStatus,
359        view_image_tool_available: bool,
360        previous_view_image_visible: bool,
361        next_view_image_visible: bool,
362        previous_active_visibility_revision: u64,
363        previous_staged_visibility_revision: u64,
364        target_identity: Box<meerkat_core::SessionLlmIdentity>,
365        target_capability_surface: Box<SessionLlmCapabilitySurface>,
366        next_visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
367        next_capability_base_filter: meerkat_core::ToolFilter,
368        next_active_visibility_revision: u64,
369        tool_visibility_delta: Box<SessionToolVisibilityDelta>,
370    },
371    StagePersistentFilter {
372        session_id: SessionId,
373        filter: meerkat_core::ToolFilter,
374        witnesses:
375            std::collections::BTreeMap<meerkat_core::ToolName, meerkat_core::ToolVisibilityWitness>,
376    },
377    RequestDeferredTools {
378        session_id: SessionId,
379        authorities: Vec<meerkat_core::DeferredToolLoadAuthority>,
380    },
381    /// Publish the committed visible tool set through the machine dispatch.
382    ///
383    /// TLA+ source: VisibleSurfacesMatchAppliedStateInvariant —
384    /// the visible-set publication must route through the canonical command
385    /// path and be gated on session existence and non-Destroyed state.
386    PublishCommittedVisibleSet {
387        session_id: SessionId,
388        visibility_state: Box<meerkat_core::SessionToolVisibilityState>,
389    },
390    SetPeerIngressContext {
391        session_id: SessionId,
392        keep_alive: bool,
393        comms_runtime: Option<Arc<dyn CommsRuntime>>,
394        /// When present, the peer-ingress mutation is authorized only for
395        /// this exact committed executor attachment. A delayed publisher for
396        /// attachment A can therefore never configure replacement B.
397        expected_attachment: Option<crate::RuntimeExecutorAttachmentWitness>,
398        /// Mob-owned path sets this to the spawning mob's id so the DSL
399        /// transitions to `PeerIngressOwnerKind::MobOwned` rather than
400        /// `SessionOwned`. Session-owned and detach paths leave it `None`.
401        mob_id: Option<crate::meerkat_machine::dsl::MobId>,
402    },
403    NotifyDrainExited {
404        session_id: SessionId,
405        reason: DrainExitReason,
406    },
407    AbortAll,
408    Abort {
409        session_id: SessionId,
410    },
411    Wait {
412        session_id: SessionId,
413    },
414    Ingest {
415        runtime_id: LogicalRuntimeId,
416        input: Input,
417    },
418    PublishEvent {
419        event: RuntimeEventEnvelope,
420    },
421    Retire {
422        runtime_id: LogicalRuntimeId,
423    },
424    Recycle {
425        runtime_id: LogicalRuntimeId,
426    },
427    Reset {
428        runtime_id: LogicalRuntimeId,
429    },
430    Recover {
431        runtime_id: LogicalRuntimeId,
432    },
433    Destroy {
434        runtime_id: LogicalRuntimeId,
435    },
436    RuntimeState {
437        runtime_id: LogicalRuntimeId,
438    },
439    ResolvedSessionLlmCapabilities {
440        session_id: SessionId,
441    },
442    ConfigureModelRoutingBaseline {
443        session_id: SessionId,
444        baseline_model: ModelId,
445        realtime_capable: bool,
446    },
447    SessionModelRoutingStatus {
448        session_id: SessionId,
449    },
450    RequestSwitchTurn {
451        session_id: SessionId,
452        request: Box<SwitchTurnRequest>,
453    },
454    AdmitModelRoutingAssistantTurn {
455        session_id: SessionId,
456    },
457    BeginImageOperation {
458        session_id: SessionId,
459        request: Box<ImageOperationRoutingRequest>,
460    },
461    DenyImageOperationPlan {
462        session_id: SessionId,
463        operation_id: ImageOperationId,
464        reason: ImageOperationDenialReason,
465    },
466    ActivateImageOperationOverride {
467        session_id: SessionId,
468        operation_id: ImageOperationId,
469    },
470    ClassifyImageOperationTerminal {
471        session_id: SessionId,
472        operation_id: ImageOperationId,
473        observation: ImageProviderTerminalObservation,
474        provider_text: ProviderTextDisposition,
475    },
476    CompleteImageOperation {
477        session_id: SessionId,
478        operation_id: ImageOperationId,
479        terminal: ImageOperationTerminalClass,
480    },
481    RestoreImageOperationOverride {
482        session_id: SessionId,
483        operation_id: ImageOperationId,
484    },
485    LoadBoundaryReceipt {
486        runtime_id: LogicalRuntimeId,
487        run_id: LifecycleRunId,
488        sequence: u64,
489    },
490    AcceptWithCompletion {
491        session_id: SessionId,
492        input: Input,
493        register_completion: bool,
494        member_residency: MemberResidencyExpectation,
495        expected_attachment: Option<RuntimeExecutorAttachmentWitness>,
496    },
497    AcceptWithoutWake {
498        session_id: SessionId,
499        input: Input,
500    },
501}
502
503#[derive(Debug, Clone)]
504pub(crate) enum MemberResidencyExpectation {
505    Unfenced,
506    PeerOnly,
507    Placed(meerkat_contracts::wire::supervisor_bridge::BridgeMemberIncarnation),
508}
509
510#[derive(Debug, Clone)]
511pub(crate) struct MeerkatMachineRunFailure {
512    pub source: Option<dsl::RunFailureSourceKind>,
513    pub machine_terminal_failure_observed: bool,
514    pub machine_terminal_error: Option<meerkat_core::TurnErrorMetadata>,
515    pub error: String,
516}
517
518impl MeerkatMachineRunFailure {
519    pub(crate) fn from_machine_terminal_failure(error: meerkat_core::TurnErrorMetadata) -> Self {
520        let detail = error
521            .detail
522            .clone()
523            .unwrap_or_else(|| "machine terminal failure".to_string());
524        Self {
525            source: None,
526            machine_terminal_failure_observed: true,
527            machine_terminal_error: Some(error),
528            error: detail,
529        }
530    }
531}
532
533#[derive(Debug)]
534#[allow(clippy::large_enum_variant)]
535pub(crate) enum MeerkatMachineCommandResult {
536    AcceptOutcome(AcceptOutcome),
537    AcceptWithCompletion {
538        outcome: AcceptOutcome,
539        handle: Option<crate::completion::CompletionHandle>,
540        #[cfg_attr(not(test), allow(dead_code))]
541        admission_signal: crate::driver::ephemeral::PostAdmissionSignal,
542    },
543    Unit,
544    Bool(bool),
545    Spawned(bool),
546    OpsLifecycleRegistry(Option<Arc<crate::ops_lifecycle::RuntimeOpsLifecycleRegistry>>),
547    Bindings(meerkat_core::SessionRuntimeBindings),
548    InputState(Option<StoredInputState>),
549    InteractionTerminalStatus(
550        Option<crate::terminal_status::Sourced<crate::terminal_status::InteractionTerminalReport>>,
551    ),
552    RunTerminalStatus(crate::terminal_status::Sourced<crate::terminal_status::RunTerminalReport>),
553    ActiveInputs(Vec<InputId>),
554    LlmReconfigured(SessionLlmReconfigureReport),
555    VisibilityRevision(meerkat_core::ToolScopeRevision),
556    VisibilityPublished(meerkat_core::SessionToolVisibilityState),
557    RetireReport(RetireReport),
558    RecycleReport(RecycleReport),
559    ResetReport(ResetReport),
560    RecoveryReport(RecoveryReport),
561    DestroyReport(DestroyReport),
562    RuntimeState(RuntimeState),
563    ResolvedSessionLlmCapabilities(Option<SessionLlmCapabilitySurface>),
564    SessionModelRoutingStatus(SessionModelRoutingStatus),
565    SwitchTurnControlResult(SwitchTurnControlResult),
566    ImageOperationRoutingResult(ImageOperationRoutingResult),
567    ImageOperationPhase(ImageOperationPhase),
568    ImageOperationTerminalClass(ImageOperationTerminalClass),
569    BoundaryReceipt(Option<RunBoundaryReceipt>),
570}
571
572#[doc(hidden)]
573#[must_use]
574pub fn canonical_meerkat_machine_command_manifest() -> IndexSet<&'static str> {
575    canonical_meerkat_machine_command_input_variant_manifest()
576        .into_iter()
577        .map(|variant| variant.as_str())
578        .collect()
579}
580
581#[doc(hidden)]
582#[must_use]
583pub fn canonical_meerkat_machine_command_input_variant_manifest()
584-> IndexSet<MeerkatMachineInputVariant> {
585    canonical_meerkat_machine_command_classifications()
586        .into_iter()
587        .flat_map(|record| record.classification.catalog_input_variants())
588        .collect()
589}
590
591#[doc(hidden)]
592#[must_use]
593pub fn canonical_meerkat_machine_runtime_internal_manifest() -> IndexSet<&'static str> {
594    canonical_meerkat_machine_runtime_internal_input_variant_manifest()
595        .into_iter()
596        .map(|variant| variant.as_str())
597        .collect()
598}
599
600#[doc(hidden)]
601#[must_use]
602pub fn canonical_meerkat_machine_runtime_internal_input_variant_manifest()
603-> IndexSet<MeerkatMachineInputVariant> {
604    canonical_meerkat_machine_runtime_internal_classifications()
605        .into_iter()
606        .map(|record| record.input.input_variant())
607        .collect()
608}
609
610#[doc(hidden)]
611#[must_use]
612pub fn canonical_meerkat_machine_runtime_internal_fieldless_input_variant_manifest()
613-> IndexSet<MeerkatMachineInputVariant> {
614    MeerkatMachineFieldlessRuntimeInternalInput::ALL
615        .iter()
616        .copied()
617        .map(MeerkatMachineFieldlessRuntimeInternalInput::input_variant)
618        .collect()
619}
620
621macro_rules! meerkat_machine_runtime_internal_inputs {
622    ($($reason:ident => [$($variant:ident),+ $(,)?]),+ $(,)?) => {
623        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
624        pub enum MeerkatMachineRuntimeInternalInput {
625            $($($variant),+),+
626        }
627
628        impl MeerkatMachineRuntimeInternalInput {
629            pub const ALL: &'static [Self] = &[
630                $($(Self::$variant),+),+
631            ];
632
633            pub const CLASSIFICATIONS: &'static [MeerkatMachineRuntimeInternalClassificationRecord] = &[
634                $($(
635                    MeerkatMachineRuntimeInternalClassificationRecord {
636                        input: Self::$variant,
637                        reason: MeerkatMachineRuntimeInternalReason::$reason,
638                    },
639                )+)+
640            ];
641
642            #[must_use]
643            pub const fn input_variant(self) -> MeerkatMachineInputVariant {
644                match self {
645                    $($(Self::$variant => MeerkatMachineInputVariant::$variant,)+)+
646                }
647            }
648
649            #[must_use]
650            pub const fn reason(self) -> MeerkatMachineRuntimeInternalReason {
651                match self {
652                    $(
653                        $(Self::$variant)|+ => MeerkatMachineRuntimeInternalReason::$reason,
654                    )+
655                }
656            }
657        }
658    };
659}
660
661#[derive(Debug, Clone, Copy, PartialEq, Eq)]
662pub enum MeerkatMachineRuntimeInternalReason {
663    InputQueueLifecycle,
664    OperationLifecycle,
665    RunExecutionLifecycle,
666    CancellationLifecycle,
667    LiveTopologyReconfiguration,
668    InteractionStreamLifecycle,
669    EventStreamLifecycle,
670    CommsIngressLifecycle,
671    SupervisorTrustLifecycle,
672    MobOperatorAuthorityLifecycle,
673    PeerRequestLifecycle,
674    VisibilityAuthorityLifecycle,
675    DeferredSessionLifecycle,
676    ExtractionLifecycle,
677    McpServerLifecycle,
678    ModelRoutingLifecycle,
679    ExternalSurfaceLifecycle,
680    FailureRecoveryLifecycle,
681    UserInterruptDispatch,
682    SessionUnregisterDrainLifecycle,
683}
684
685#[derive(Debug, Clone, Copy, PartialEq, Eq)]
686pub struct MeerkatMachineRuntimeInternalClassificationRecord {
687    pub input: MeerkatMachineRuntimeInternalInput,
688    pub reason: MeerkatMachineRuntimeInternalReason,
689}
690
691meerkat_machine_runtime_internal_inputs!(
692    InputQueueLifecycle => [
693        AbandonInput,
694        AdvanceSessionContext,
695        ArchiveTerminalInput,
696        BudgetExhausted,
697        ChangeLane,
698        CoalesceInput,
699        ConsumeInput,
700        ConsumeOnAccept,
701        MarkApplied,
702        MarkAppliedPendingConsumption,
703        DeferInputBehindBacklog,
704        PrioritizeInput,
705        QueueAccepted,
706        RecoverAdmittedInput,
707        RecoverInputLifecycle,
708        ResolveAdmissionIdempotency,
709        ResolveAdmissionPlan,
710        ResolveAdmissionValidation,
711        ResolveInputPublicLifecycle,
712        ResolveInputPublicTerminalOutcome,
713        ResolveTranscriptEditAdmission,
714        RegisterAcceptedIdempotency,
715        ResolveStagedRollback,
716        RetryRequested,
717        RollbackStaged,
718        StageForRun,
719        StartConversationRun,
720        StartImmediateAppend,
721        SteerAccepted,
722        SupersedeInput,
723        AuthorizeStoredInputStateSeed,
724        ClassifyInputTerminality,
725        ClassifyRecoveredInputDurability,
726        ClassifyRuntimeLoopQueueAdmission,
727        NormalizeRecoveredInputLifecycle,
728    ],
729    OperationLifecycle => [
730        AbortOp,
731        CancelOp,
732        CancelWaitAll,
733        ClassifyOperationCompletionFeed,
734        ClassifyOperationCompletionWake,
735        ClassifyOperationDurability,
736        ClassifyOperationPublicResult,
737        ClassifyOperationTerminality,
738        ClassifyOperationTransitionIdempotence,
739        ClassifyRecoveredOperationRecord,
740        CollectCompletedOp,
741        CompleteOp,
742        EvictCompletedOp,
743        FailOp,
744        IncrementAttemptCount,
745        OpsBarrierSatisfied,
746        PeerReadyOp,
747        ProgressReportedOp,
748        RecoverCompletionFeedEntry,
749        RegisterOp,
750        RegisterPendingOps,
751        RecoverCompletionConsumerCursors,
752        RecoverOpRecord,
753        RecoverOpsCompletionCursor,
754        ResolveOpLifecycleTransitionRejection,
755        ResolveRuntimeOpsLifecycleDurability,
756        ResolveWaitAllAdmission,
757        RequestWaitAll,
758        RetireCompletedOp,
759        RetireRequestedOp,
760        RollbackUnreturnedOp,
761        SatisfyWaitAll,
762        StartOp,
763        TerminateOp,
764    ],
765    RunExecutionLifecycle => [
766        AcknowledgeTerminal,
767        CallbackPending,
768        AdvanceAgentCompletionCursor,
769        AdvanceRuntimeInjectedCompletionCursor,
770        AdvanceRuntimeObservedCompletionCursor,
771        BoundaryComplete,
772        BoundaryContinue,
773        ClassifyAssistantOutput,
774        ClassifyCallTimeout,
775        ClassifyTurnTerminalCauseClass,
776        ClassifyTurnTerminality,
777        ClearSessionLlmState,
778        Commit,
779        CommitTerminalBoundarySequence,
780        Fail,
781        HydrateSessionLlmState,
782        LlmReturnedTerminal,
783        LlmReturnedToolCalls,
784        LiveBoundaryUnavailable,
785        Prepare,
786        PrimitiveApplied,
787        RecordBoundarySeq,
788        ResolveLiveBoundaryContextReceipt,
789        ResolveRuntimeCompletionCleanup,
790        ResolveRuntimeCompletionResult,
791        ResolveRuntimeCompletionWaitFailure,
792        ResolveTurnSurfaceResult,
793        RollbackRun,
794        RunCompleted,
795        RunFailed,
796        RuntimeExecutorExited,
797        ServiceTurnCommitted,
798        TimeBudgetExceeded,
799        ToolCallsResolved,
800        TurnLimitReached,
801    ],
802    CancellationLifecycle => [
803        AbortCancelAfterBoundaryDispatch,
804        CancelNow,
805        CancelRun,
806        CancellationObserved,
807        ForceCancelNoRun,
808        RequestCancelAfterBoundary,
809        RunCancelled,
810    ],
811    LiveTopologyReconfiguration => [
812        AbandonLiveOpenAdmission,
813        CompleteUntilChangedSwitchTurnReconfigure,
814        RecordLiveChannelRequestRejected,
815        RecordLiveChannelStatus,
816        RecordLiveCloseClosed,
817        RecordLiveCommandAccepted,
818        RecordLiveCommandRejected,
819        RecordLiveRefreshQueued,
820        ResolveLiveOpenAdmission,
821    ],
822    InteractionStreamLifecycle => [
823        InteractionStreamAbandoned,
824        InteractionStreamAttached,
825        InteractionStreamClosedEarly,
826        InteractionStreamCompleted,
827        InteractionStreamExpired,
828        InteractionStreamReserved,
829    ],
830    EventStreamLifecycle => [
831        RecordMobEventStreamOpened,
832        RecordMobEventStreamTerminated,
833        RecordSessionEventStreamOpened,
834        RecordSessionEventStreamTerminated,
835        ResolveMobEventStreamClose,
836        ResolveSessionEventStreamClose,
837    ],
838    CommsIngressLifecycle => [
839        AddDirectPeerEndpoint,
840        ApplyMobPeerOverlay,
841        AttachMobIngress,
842        AttachSessionIngress,
843        AuthorizeSupervisorMobPeerOverlay,
844        BindSupervisor,
845        ClearLocalEndpoint,
846        DetachIngress,
847        PeerResponseRejected,
848        PublishLocalEndpoint,
849        RemoveDirectPeerEndpoint,
850        ResolvePeerIngressDequeue,
851        ResolvePeerIngressReceive,
852        ResolveSupervisorAuthorizeAdmission,
853        ResolveSupervisorBindAdmission,
854        ResolveSupervisorBindMaterialAdmission,
855        ResolveSupervisorBridgeCommandAdmission,
856        SpawnDrain,
857        StopDrain,
858    ],
859    SupervisorTrustLifecycle => [
860        AuthorizeSupervisor,
861        PrepareTerminalSupervisorCleanupBindings,
862        RecoverRevokedSupervisorReceipt,
863        RecoverSupervisorBinding,
864        RecoverSupervisorRevocationPending,
865        RecoverSupervisorRotationOperation,
866        RecoverSupervisorRotationTerminalReceipt,
867        RefreshSupervisorBindingRoute,
868        RequestSupervisorTrustPublish,
869        ResolveSupervisorCleanupCommandAdmission,
870        ResumeSupervisorRotation,
871        RevokeSupervisor,
872        SubmitSupervisorRotation,
873        SupervisorRotationNextPublished,
874        SupervisorRotationPreviousRevoked,
875        SupervisorTrustEdgePublishFailed,
876        SupervisorTrustEdgePublished,
877        SupervisorTrustEdgeRevokeFailed,
878        SupervisorTrustEdgeRevoked,
879        ObserveSupervisorRotation,
880    ],
881    MobOperatorAuthorityLifecycle => [
882        GrantMobOperatorManageMob,
883        ResolveMobOperatorCreateAuthority,
884        RestoreMobOperatorAuthority,
885        SetMobOperatorCreateAuthority,
886        SetMobOperatorProfileMutation,
887        SetMobOperatorSpawnProfilesInMob,
888    ],
889    PeerRequestLifecycle => [
890        PeerRequestReceived,
891        PeerRequestSendFailed,
892        PeerRequestSent,
893        PeerRequestTimedOut,
894        PeerResponseProgressArrived,
895        PeerResponseReplied,
896        PeerResponseTerminalArrived,
897    ],
898    VisibilityAuthorityLifecycle => [
899        CommitDeferredNames,
900        CommitVisibilityFilter,
901        ClearTurnToolOverlay,
902        ReplaceDeferredToolAuthorityCatalog,
903        ReplaceFilterToolAuthorityCatalog,
904        SetTurnToolOverlay,
905        StageDeferredNames,
906        StageVisibilityFilter,
907        SurfaceSetRemovalTimeout,
908        ReplaceVisibilityState,
909    ],
910    DeferredSessionLifecycle => [
911        AbandonDeferredSessionPromotion,
912        AuthorizeDeferredSessionMachineArchivedResume,
913        BeginDeferredSessionArchive,
914        BeginDeferredSessionPromotion,
915        DropDeferredSession,
916        FinishDeferredSessionArchive,
917        FinishDeferredSessionPromotion,
918        RestoreDeferredSessionArchive,
919        StageDeferredSession,
920        UpdateDeferredSessionKeepAlive,
921        UpdateDeferredSessionLlmIdentity,
922    ],
923    ExtractionLifecycle => [
924        EnterExtraction,
925        ExtractionFailed,
926        ExtractionStart,
927        ExtractionValidationFailed,
928        ExtractionValidationPassed,
929    ],
930    McpServerLifecycle => [
931        McpServerConnectPending,
932        McpServerConnected,
933        McpServerDisconnected,
934        McpServerFailed,
935        McpServerReload,
936    ],
937    ModelRoutingLifecycle => [
938        CommitStickyModelFallback,
939        ModelRoutingStatus,
940        RequestFiniteSwitchTurn,
941        RequestUntilChangedSwitchTurn,
942        SetModelRoutingBaseline,
943    ],
944    ExternalSurfaceLifecycle => [
945        AdmitSurfaceRequest,
946        CancelSurfaceRequest,
947        ClassifySurfaceRequestTerminal,
948        FinishSurfaceRequestUnpublished,
949        PublishOrCancelSurfaceRequest,
950        PublishSurfaceRequest,
951        RecordLiveWebrtcAnswerAccepted,
952        RecordLiveWebrtcTokenIssued,
953        RecordLiveWebsocketTokenIssued,
954        ResolveLiveWebrtcAnswerAdmission,
955        ResolveLiveWebsocketTokenAdmission,
956        SurfaceApplyBoundary,
957        SurfaceCallFinished,
958        SurfaceCallStarted,
959        SurfaceFinalizeRemovalClean,
960        SurfaceFinalizeRemovalForced,
961        SurfaceMarkPendingFailed,
962        SurfaceMarkPendingSucceeded,
963        SurfaceRegister,
964        SurfaceShutdown,
965        SurfaceSnapshotAligned,
966        SurfaceStageAdd,
967        SurfaceStageReload,
968        SurfaceStageRemove,
969    ],
970    FailureRecoveryLifecycle => [
971        AuthorizeDurableTailRecovery,
972        AuthorizeInteractionTerminalOutboxAdoption,
973        ClassifyLlmFailureRecovery,
974        ClassifyRuntimeAuthorityReconciliation,
975        ClassifyRuntimeLifecycleDurability,
976        ClassifyRuntimeLifecycleState,
977        FatalFailure,
978        RecoverableFailure,
979        RecoverRuntimeCompletionResultCorrelation,
980        ResolveVisibleRuntimePhase,
981    ],
982    UserInterruptDispatch => [
983        InterruptCurrentRun,
984        ResolveUserInterruptPublicResult,
985    ],
986    SessionUnregisterDrainLifecycle => [
987        BeginUnregisterUnservedAttachment,
988        BeginUnregisterSession,
989        CommsDrainExitedForUnregister,
990        CompletionWaitersResolvedForUnregister,
991        RuntimeLoopStoppedForUnregister,
992    ],
993);
994
995macro_rules! meerkat_machine_fieldless_runtime_internal_inputs {
996    ($($authority:ident => [$($variant:ident),+ $(,)?]),+ $(,)?) => {
997        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
998        pub enum MeerkatMachineFieldlessRuntimeInternalInput {
999            $($($variant),+),+
1000        }
1001
1002        impl MeerkatMachineFieldlessRuntimeInternalInput {
1003            pub const ALL: &'static [Self] = &[
1004                $($(Self::$variant),+),+
1005            ];
1006
1007            #[must_use]
1008            pub const fn runtime_internal_input(self) -> MeerkatMachineRuntimeInternalInput {
1009                match self {
1010                    $($(Self::$variant => MeerkatMachineRuntimeInternalInput::$variant,)+)+
1011                }
1012            }
1013
1014            #[must_use]
1015            pub const fn input_variant(self) -> MeerkatMachineInputVariant {
1016                self.runtime_internal_input().input_variant()
1017            }
1018
1019            #[must_use]
1020            pub const fn authority(self) -> MeerkatMachineFieldlessRuntimeInternalAuthority {
1021                match self {
1022                    $(
1023                        $(Self::$variant)|+ => MeerkatMachineFieldlessRuntimeInternalAuthority::$authority,
1024                    )+
1025                }
1026            }
1027
1028            #[must_use]
1029            pub const fn requires_typed_runtime_internal_stager(self) -> bool {
1030                matches!(
1031                    self.authority(),
1032                    MeerkatMachineFieldlessRuntimeInternalAuthority::UserInterruptDispatch
1033                )
1034            }
1035
1036            pub(crate) const fn dsl_input_variant(self) -> dsl::MeerkatMachineInputVariant {
1037                match self {
1038                    $($(Self::$variant => dsl::MeerkatMachineInputVariant::$variant,)+)+
1039                }
1040            }
1041
1042            pub(crate) fn dsl_input(self) -> dsl::MeerkatMachineInput {
1043                match self {
1044                    $($(Self::$variant => dsl::MeerkatMachineInput::$variant,)+)+
1045                }
1046            }
1047
1048            pub(crate) fn from_dsl_input_variant(
1049                variant: dsl::MeerkatMachineInputVariant,
1050            ) -> Option<Self> {
1051                Self::ALL
1052                    .iter()
1053                    .copied()
1054                    .find(|input| input.dsl_input_variant() == variant)
1055            }
1056
1057            pub(crate) fn reject_raw_dsl_input(
1058                input: &dsl::MeerkatMachineInput,
1059            ) -> Result<(), String> {
1060                if let Some(fieldless) = Self::from_dsl_input_variant(input.variant())
1061                    && fieldless.requires_typed_runtime_internal_stager()
1062                {
1063                    let variant = fieldless.input_variant();
1064                    return Err(format!(
1065                        "fieldless runtime-internal input {variant:?} must use typed runtime-internal staging authority"
1066                    ));
1067                }
1068                Ok(())
1069            }
1070        }
1071    };
1072}
1073
1074meerkat_machine_fieldless_runtime_internal_inputs!(
1075    RuntimeOwner => [
1076        RuntimeExecutorExited,
1077        ForceCancelNoRun,
1078        CancelWaitAll,
1079        StopDrain,
1080        SurfaceShutdown,
1081        DetachIngress,
1082        ClearLocalEndpoint,
1083    ],
1084    UserInterruptDispatch => [
1085        InterruptCurrentRun,
1086    ],
1087);
1088
1089#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1090pub enum MeerkatMachineFieldlessRuntimeInternalAuthority {
1091    RuntimeOwner,
1092    UserInterruptDispatch,
1093}
1094
1095#[doc(hidden)]
1096#[must_use]
1097pub fn canonical_meerkat_machine_runtime_internal_classifications()
1098-> Vec<MeerkatMachineRuntimeInternalClassificationRecord> {
1099    MeerkatMachineRuntimeInternalInput::CLASSIFICATIONS.to_vec()
1100}
1101
1102/// Closed typed manifest of supervisor bridge wire command kinds
1103/// (`meerkat_contracts::wire::supervisor_bridge::BridgeCommand` variants).
1104///
1105/// The wire enum is `#[non_exhaustive]`, so no match over it outside
1106/// `meerkat-contracts` can be compiler-checked for completeness. This manifest
1107/// is the runtime's completeness authority instead:
1108/// `supervisor_bridge_command_manifest_matches_wire_enum` pins it against the
1109/// wire enum's variant list and
1110/// `supervisor_bridge_command_realization_matches_drain_dispatch` pins each
1111/// kind's realization posture against the `comms_drain` bridge dispatch match,
1112/// so a new wire command or dispatch arm cannot land without a typed
1113/// admission classification here.
1114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1115pub enum SupervisorBridgeCommandKind {
1116    BindMember,
1117    AuthorizeSupervisor,
1118    ObserveSupervisorRotation,
1119    RevokeSupervisor,
1120    DeliverMemberInput,
1121    ObserveMember,
1122    InterruptMember,
1123    HardCancelMember,
1124    CancelTrackedMemberInput,
1125    RetireMember,
1126    DestroyMember,
1127    WireMember,
1128    UnwireMember,
1129    DeclareMemberOutboundTaint,
1130    // Multi-host (protocol V4) member-addressed family (plan §6.4/§16.4):
1131    // admission-classified ahead of realization; the comms drain stays
1132    // fail-closed until the execution arms land.
1133    ReadMemberHistory,
1134    PollMemberEvents,
1135    OpenMemberLiveChannel,
1136    CloseMemberLiveChannel,
1137    MemberLiveChannelStatus,
1138    ControlMemberLiveChannel,
1139    // Multi-host (protocol V4) host-addressed + member-originated families:
1140    // admitted by `MobHostBindingAuthority` on the host daemon / `MobMachine`
1141    // on the controlling host, never by MeerkatMachine on a member runtime.
1142    BindHost,
1143    RebindHost,
1144    RevokeHost,
1145    MaterializeMember,
1146    ReleaseMember,
1147    InstallPeerTrust,
1148    RemovePeerTrust,
1149    HostStatus,
1150    MemberOperatorRequest,
1151}
1152
1153impl SupervisorBridgeCommandKind {
1154    pub const ALL: &'static [Self] = &[
1155        Self::BindMember,
1156        Self::AuthorizeSupervisor,
1157        Self::ObserveSupervisorRotation,
1158        Self::RevokeSupervisor,
1159        Self::DeliverMemberInput,
1160        Self::ObserveMember,
1161        Self::InterruptMember,
1162        Self::HardCancelMember,
1163        Self::CancelTrackedMemberInput,
1164        Self::RetireMember,
1165        Self::DestroyMember,
1166        Self::WireMember,
1167        Self::UnwireMember,
1168        Self::DeclareMemberOutboundTaint,
1169        Self::ReadMemberHistory,
1170        Self::PollMemberEvents,
1171        Self::OpenMemberLiveChannel,
1172        Self::CloseMemberLiveChannel,
1173        Self::MemberLiveChannelStatus,
1174        Self::ControlMemberLiveChannel,
1175        Self::BindHost,
1176        Self::RebindHost,
1177        Self::RevokeHost,
1178        Self::MaterializeMember,
1179        Self::ReleaseMember,
1180        Self::InstallPeerTrust,
1181        Self::RemovePeerTrust,
1182        Self::HostStatus,
1183        Self::MemberOperatorRequest,
1184    ];
1185
1186    /// Wire enum variant identifier this kind mirrors.
1187    #[must_use]
1188    pub const fn variant_name(self) -> &'static str {
1189        match self {
1190            Self::BindMember => "BindMember",
1191            Self::AuthorizeSupervisor => "AuthorizeSupervisor",
1192            Self::ObserveSupervisorRotation => "ObserveSupervisorRotation",
1193            Self::RevokeSupervisor => "RevokeSupervisor",
1194            Self::DeliverMemberInput => "DeliverMemberInput",
1195            Self::ObserveMember => "ObserveMember",
1196            Self::InterruptMember => "InterruptMember",
1197            Self::HardCancelMember => "HardCancelMember",
1198            Self::CancelTrackedMemberInput => "CancelTrackedMemberInput",
1199            Self::RetireMember => "RetireMember",
1200            Self::DestroyMember => "DestroyMember",
1201            Self::WireMember => "WireMember",
1202            Self::UnwireMember => "UnwireMember",
1203            Self::DeclareMemberOutboundTaint => "DeclareMemberOutboundTaint",
1204            Self::ReadMemberHistory => "ReadMemberHistory",
1205            Self::PollMemberEvents => "PollMemberEvents",
1206            Self::OpenMemberLiveChannel => "OpenMemberLiveChannel",
1207            Self::CloseMemberLiveChannel => "CloseMemberLiveChannel",
1208            Self::MemberLiveChannelStatus => "MemberLiveChannelStatus",
1209            Self::ControlMemberLiveChannel => "ControlMemberLiveChannel",
1210            Self::BindHost => "BindHost",
1211            Self::RebindHost => "RebindHost",
1212            Self::RevokeHost => "RevokeHost",
1213            Self::MaterializeMember => "MaterializeMember",
1214            Self::ReleaseMember => "ReleaseMember",
1215            Self::InstallPeerTrust => "InstallPeerTrust",
1216            Self::RemovePeerTrust => "RemovePeerTrust",
1217            Self::HostStatus => "HostStatus",
1218            Self::MemberOperatorRequest => "MemberOperatorRequest",
1219        }
1220    }
1221
1222    /// MeerkatMachine admission route for this command on a member runtime.
1223    #[must_use]
1224    pub const fn admission_route(self) -> SupervisorBridgeCommandAdmissionRoute {
1225        match self {
1226            Self::BindMember => SupervisorBridgeCommandAdmissionRoute::SupervisorBind,
1227            Self::AuthorizeSupervisor => SupervisorBridgeCommandAdmissionRoute::SupervisorAuthorize,
1228            Self::ObserveSupervisorRotation => {
1229                SupervisorBridgeCommandAdmissionRoute::SupervisorRotationObservation
1230            }
1231            Self::RevokeSupervisor
1232            | Self::DeliverMemberInput
1233            | Self::ObserveMember
1234            | Self::InterruptMember
1235            | Self::HardCancelMember
1236            | Self::CancelTrackedMemberInput
1237            | Self::RetireMember
1238            | Self::DestroyMember
1239            | Self::WireMember
1240            | Self::UnwireMember
1241            | Self::DeclareMemberOutboundTaint
1242            | Self::ReadMemberHistory
1243            | Self::PollMemberEvents
1244            | Self::OpenMemberLiveChannel
1245            | Self::CloseMemberLiveChannel
1246            | Self::MemberLiveChannelStatus
1247            | Self::ControlMemberLiveChannel => {
1248                SupervisorBridgeCommandAdmissionRoute::SupervisorBridgeCommand
1249            }
1250            Self::BindHost
1251            | Self::RebindHost
1252            | Self::RevokeHost
1253            | Self::MaterializeMember
1254            | Self::ReleaseMember
1255            | Self::InstallPeerTrust
1256            | Self::RemovePeerTrust
1257            | Self::HostStatus
1258            | Self::MemberOperatorRequest => {
1259                SupervisorBridgeCommandAdmissionRoute::NotMemberAddressed
1260            }
1261        }
1262    }
1263
1264    /// Current realization posture: member-runtime comms drain arm,
1265    /// off-drain responder, or fail-closed unsupported.
1266    #[must_use]
1267    pub const fn realization(self) -> SupervisorBridgeCommandRealization {
1268        match self {
1269            Self::BindMember
1270            | Self::AuthorizeSupervisor
1271            | Self::ObserveSupervisorRotation
1272            | Self::RevokeSupervisor
1273            | Self::DeliverMemberInput
1274            | Self::ObserveMember
1275            | Self::InterruptMember
1276            | Self::RetireMember
1277            | Self::DestroyMember
1278            | Self::WireMember
1279            | Self::UnwireMember
1280            | Self::DeclareMemberOutboundTaint
1281            // Phase 6 (§7.4): member-input cancellation + the observation
1282            // pair gained member-drain arms (DEC-P6E-1/3/6/7).
1283            | Self::HardCancelMember
1284            | Self::CancelTrackedMemberInput
1285            | Self::ReadMemberHistory
1286            | Self::PollMemberEvents
1287            // Phase 6b (§16): the live-channel family gained member-drain
1288            // arms served through the MemberLiveHost slot (ADJ-P6B-1); an
1289            // absent slot rejects typed LiveTransportUnavailable at the arm.
1290            | Self::OpenMemberLiveChannel
1291            | Self::CloseMemberLiveChannel
1292            | Self::MemberLiveChannelStatus
1293            | Self::ControlMemberLiveChannel => SupervisorBridgeCommandRealization::Realized,
1294            // Host-addressed bind/rebind (phase 2), materialize/release/
1295            // status (phase 3), and the peer-trust pair (phase 4) are served
1296            // by the mob host daemon's serving loop, never a member drain
1297            // arm (ADJ-13; admission is MobHostBindingAuthority-owned — the
1298            // trust mutation itself applies through the member session's
1299            // machine-gated direct-peer-endpoint seam).
1300            Self::BindHost
1301            | Self::RebindHost
1302            | Self::RevokeHost
1303            | Self::MaterializeMember
1304            | Self::ReleaseMember
1305            | Self::InstallPeerTrust
1306            | Self::RemovePeerTrust
1307            | Self::HostStatus => SupervisorBridgeCommandRealization::RealizedOffDrain {
1308                by: OffDrainResponder::HostDaemon,
1309            },
1310            // Member-originated operator requests are served by the
1311            // controlling host's supervisor-inbox responder (ADJ-13;
1312            // admission is MobMachine's ResolveMemberOperatorAdmission).
1313            Self::MemberOperatorRequest => SupervisorBridgeCommandRealization::RealizedOffDrain {
1314                by: OffDrainResponder::ControllingSupervisorInbox,
1315            },
1316        }
1317    }
1318}
1319
1320/// MeerkatMachine admission route for a supervisor bridge command arriving at
1321/// a member runtime.
1322#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1323pub enum SupervisorBridgeCommandAdmissionRoute {
1324    /// Bind bootstrap ladder (`ResolveSupervisorBindAdmission` +
1325    /// `ResolveSupervisorBindMaterialAdmission`): establishes initial
1326    /// supervisor authority, so it cannot require a bound supervisor.
1327    SupervisorBind,
1328    /// Supervisor rotation ladder (`ResolveSupervisorAuthorizeAdmission`).
1329    SupervisorAuthorize,
1330    /// Typed observation of the machine-owned supervisor rotation operation.
1331    SupervisorRotationObservation,
1332    /// Bound-supervisor command ladder
1333    /// (`ResolveSupervisorBridgeCommandAdmission`).
1334    ///
1335    /// v1 posture (multi-host plan §6.4, deliberate and recorded): the ladder
1336    /// guards on bound supervisor identity + epoch + sender only. There is no
1337    /// member-side fence or topology-epoch validation on delivery — fences
1338    /// are validated supervisor-side on submit (`StaleFenceToken`).
1339    SupervisorBridgeCommand,
1340    /// Never admitted by MeerkatMachine: host-addressed commands are admitted
1341    /// by `MobHostBindingAuthority` on the host daemon; the member-originated
1342    /// operator request is admitted by `MobMachine` on the controlling host.
1343    NotMemberAddressed,
1344}
1345
1346impl SupervisorBridgeCommandAdmissionRoute {
1347    /// Generated MeerkatMachine admission inputs staged for this route.
1348    #[must_use]
1349    pub const fn admission_inputs(self) -> &'static [MeerkatMachineRuntimeInternalInput] {
1350        match self {
1351            Self::SupervisorBind => &[
1352                MeerkatMachineRuntimeInternalInput::ResolveSupervisorBindAdmission,
1353                MeerkatMachineRuntimeInternalInput::ResolveSupervisorBindMaterialAdmission,
1354            ],
1355            Self::SupervisorAuthorize => {
1356                &[MeerkatMachineRuntimeInternalInput::ResolveSupervisorAuthorizeAdmission]
1357            }
1358            Self::SupervisorRotationObservation => {
1359                &[MeerkatMachineRuntimeInternalInput::ObserveSupervisorRotation]
1360            }
1361            Self::SupervisorBridgeCommand => {
1362                &[MeerkatMachineRuntimeInternalInput::ResolveSupervisorBridgeCommandAdmission]
1363            }
1364            Self::NotMemberAddressed => &[],
1365        }
1366    }
1367}
1368
1369/// Realization posture of a supervisor bridge command in the member runtime's
1370/// comms drain (the `comms_drain.rs` bridge dispatch match) or an off-drain
1371/// responder.
1372#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1373pub enum SupervisorBridgeCommandRealization {
1374    /// A dispatch arm stages admission and realizes the command today.
1375    Realized,
1376    /// Served OFF the member runtime's comms drain by a dedicated responder
1377    /// (ADJ-13): the drain keeps ZERO arms for these kinds — the drain-scan
1378    /// gate asserts `Realized` ⇔ drain arm, so an off-drain-served command
1379    /// is never recorded as a false `Realized` and never left as an
1380    /// under-describing `FailClosedUnsupported`.
1381    RealizedOffDrain { by: OffDrainResponder },
1382    /// No dispatch arm anywhere: the fail-closed `_ =>` arm replies
1383    /// `BridgeRejectionCause::Unsupported` (the `HardCancelMember` precedent).
1384    /// Admission classification is declared ahead of realization; when the
1385    /// execution arm lands it must flip this record to `Realized` (drain arm)
1386    /// or `RealizedOffDrain` (dedicated responder) in the same change-set
1387    /// (`supervisor_bridge_command_realization_matches_drain_dispatch`
1388    /// enforces the flip).
1389    FailClosedUnsupported,
1390}
1391
1392/// Which off-drain responder realizes a `RealizedOffDrain` command (ADJ-13).
1393#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1394pub enum OffDrainResponder {
1395    /// The mob host daemon's `MobHostActor` serving loop — host-addressed
1396    /// commands admitted by `MobHostBindingAuthority`.
1397    HostDaemon,
1398    /// The controlling host's supervisor-inbox responder — the
1399    /// member-originated operator family admitted by `MobMachine`
1400    /// (`ResolveMemberOperatorAdmission`).
1401    ControllingSupervisorInbox,
1402}
1403
1404/// Per-wire-command admission classification record.
1405#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1406pub struct SupervisorBridgeCommandClassificationRecord {
1407    pub kind: SupervisorBridgeCommandKind,
1408    pub route: SupervisorBridgeCommandAdmissionRoute,
1409    pub realization: SupervisorBridgeCommandRealization,
1410}
1411
1412#[doc(hidden)]
1413#[must_use]
1414pub fn canonical_supervisor_bridge_command_classifications()
1415-> Vec<SupervisorBridgeCommandClassificationRecord> {
1416    SupervisorBridgeCommandKind::ALL
1417        .iter()
1418        .copied()
1419        .map(|kind| SupervisorBridgeCommandClassificationRecord {
1420            kind,
1421            route: kind.admission_route(),
1422            realization: kind.realization(),
1423        })
1424        .collect()
1425}
1426
1427#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1428pub enum MeerkatMachineCommandClassification {
1429    CatalogInput(MeerkatMachineCatalogInput),
1430    CatalogInputs(&'static [MeerkatMachineCatalogInput]),
1431    ShellMechanic(MeerkatMachineShellMechanicReason),
1432}
1433
1434impl MeerkatMachineCommandClassification {
1435    #[must_use]
1436    pub fn catalog_inputs(self) -> Vec<MeerkatMachineCatalogInput> {
1437        match self {
1438            Self::CatalogInput(input) => vec![input],
1439            Self::CatalogInputs(inputs) => inputs.to_vec(),
1440            Self::ShellMechanic(_) => Vec::new(),
1441        }
1442    }
1443
1444    #[must_use]
1445    pub fn catalog_input_variants(self) -> Vec<MeerkatMachineInputVariant> {
1446        self.catalog_inputs()
1447            .into_iter()
1448            .map(MeerkatMachineCatalogInput::input_variant)
1449            .collect()
1450    }
1451}
1452
1453#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1454pub enum MeerkatMachineCatalogInput {
1455    RegisterSession,
1456    UnregisterSession,
1457    EnsureSessionWithExecutor,
1458    SetSilentIntents,
1459    CancelAfterBoundary,
1460    StopRuntimeExecutor,
1461    ContainsSession,
1462    SessionHasExecutor,
1463    SessionHasComms,
1464    OpsLifecycleRegistry,
1465    PrepareBindings,
1466    InputState,
1467    ListActiveInputs,
1468    ReconfigureSessionLlmIdentity,
1469    StagePersistentFilter,
1470    RequestDeferredTools,
1471    PublishCommittedVisibleSet,
1472    SetPeerIngressContext,
1473    NotifyDrainExited,
1474    AbortAll,
1475    Abort,
1476    Wait,
1477    Ingest,
1478    PublishEvent,
1479    Retire,
1480    Recycle,
1481    Reset,
1482    Recover,
1483    Destroy,
1484    RuntimeState,
1485    ModelRoutingStatus,
1486    SetModelRoutingBaseline,
1487    RequestFiniteSwitchTurn,
1488    RequestUntilChangedSwitchTurn,
1489    AdmitModelRoutingAssistantTurn,
1490    BeginImageOperation,
1491    DenyImageOperationPlan,
1492    ActivateImageOperationOverride,
1493    ClassifyImageOperationTerminal,
1494    CompleteImageOperation,
1495    RestoreImageOperationOverride,
1496    LoadBoundaryReceipt,
1497    AcceptWithCompletion,
1498    AcceptWithoutWake,
1499}
1500
1501impl MeerkatMachineCatalogInput {
1502    pub const ALL: &'static [Self] = &[
1503        Self::RegisterSession,
1504        Self::UnregisterSession,
1505        Self::EnsureSessionWithExecutor,
1506        Self::SetSilentIntents,
1507        Self::CancelAfterBoundary,
1508        Self::StopRuntimeExecutor,
1509        Self::ContainsSession,
1510        Self::SessionHasExecutor,
1511        Self::SessionHasComms,
1512        Self::OpsLifecycleRegistry,
1513        Self::PrepareBindings,
1514        Self::InputState,
1515        Self::ListActiveInputs,
1516        Self::ReconfigureSessionLlmIdentity,
1517        Self::StagePersistentFilter,
1518        Self::RequestDeferredTools,
1519        Self::PublishCommittedVisibleSet,
1520        Self::SetPeerIngressContext,
1521        Self::NotifyDrainExited,
1522        Self::AbortAll,
1523        Self::Abort,
1524        Self::Wait,
1525        Self::Ingest,
1526        Self::PublishEvent,
1527        Self::Retire,
1528        Self::Recycle,
1529        Self::Reset,
1530        Self::Recover,
1531        Self::Destroy,
1532        Self::RuntimeState,
1533        Self::ModelRoutingStatus,
1534        Self::SetModelRoutingBaseline,
1535        Self::RequestFiniteSwitchTurn,
1536        Self::RequestUntilChangedSwitchTurn,
1537        Self::AdmitModelRoutingAssistantTurn,
1538        Self::BeginImageOperation,
1539        Self::DenyImageOperationPlan,
1540        Self::ActivateImageOperationOverride,
1541        Self::ClassifyImageOperationTerminal,
1542        Self::CompleteImageOperation,
1543        Self::RestoreImageOperationOverride,
1544        Self::LoadBoundaryReceipt,
1545        Self::AcceptWithCompletion,
1546        Self::AcceptWithoutWake,
1547    ];
1548
1549    #[must_use]
1550    pub const fn input_variant(self) -> MeerkatMachineInputVariant {
1551        match self {
1552            Self::RegisterSession => MeerkatMachineInputVariant::RegisterSession,
1553            Self::UnregisterSession => MeerkatMachineInputVariant::UnregisterSession,
1554            Self::EnsureSessionWithExecutor => {
1555                MeerkatMachineInputVariant::EnsureSessionWithExecutor
1556            }
1557            Self::SetSilentIntents => MeerkatMachineInputVariant::SetSilentIntents,
1558            Self::CancelAfterBoundary => MeerkatMachineInputVariant::CancelAfterBoundary,
1559            Self::StopRuntimeExecutor => MeerkatMachineInputVariant::StopRuntimeExecutor,
1560            Self::ContainsSession => MeerkatMachineInputVariant::ContainsSession,
1561            Self::SessionHasExecutor => MeerkatMachineInputVariant::SessionHasExecutor,
1562            Self::SessionHasComms => MeerkatMachineInputVariant::SessionHasComms,
1563            Self::OpsLifecycleRegistry => MeerkatMachineInputVariant::OpsLifecycleRegistry,
1564            Self::PrepareBindings => MeerkatMachineInputVariant::PrepareBindings,
1565            Self::InputState => MeerkatMachineInputVariant::InputState,
1566            Self::ListActiveInputs => MeerkatMachineInputVariant::ListActiveInputs,
1567            Self::ReconfigureSessionLlmIdentity => {
1568                MeerkatMachineInputVariant::ReconfigureSessionLlmIdentity
1569            }
1570            Self::StagePersistentFilter => MeerkatMachineInputVariant::StagePersistentFilter,
1571            Self::RequestDeferredTools => MeerkatMachineInputVariant::RequestDeferredTools,
1572            Self::PublishCommittedVisibleSet => {
1573                MeerkatMachineInputVariant::PublishCommittedVisibleSet
1574            }
1575            Self::SetPeerIngressContext => MeerkatMachineInputVariant::SetPeerIngressContext,
1576            Self::NotifyDrainExited => MeerkatMachineInputVariant::NotifyDrainExited,
1577            Self::AbortAll => MeerkatMachineInputVariant::AbortAll,
1578            Self::Abort => MeerkatMachineInputVariant::Abort,
1579            Self::Wait => MeerkatMachineInputVariant::Wait,
1580            Self::Ingest => MeerkatMachineInputVariant::Ingest,
1581            Self::PublishEvent => MeerkatMachineInputVariant::PublishEvent,
1582            Self::Retire => MeerkatMachineInputVariant::Retire,
1583            Self::Recycle => MeerkatMachineInputVariant::Recycle,
1584            Self::Reset => MeerkatMachineInputVariant::Reset,
1585            Self::Recover => MeerkatMachineInputVariant::Recover,
1586            Self::Destroy => MeerkatMachineInputVariant::Destroy,
1587            Self::RuntimeState => MeerkatMachineInputVariant::RuntimeState,
1588            Self::ModelRoutingStatus => MeerkatMachineInputVariant::ModelRoutingStatus,
1589            Self::SetModelRoutingBaseline => MeerkatMachineInputVariant::SetModelRoutingBaseline,
1590            Self::RequestFiniteSwitchTurn => MeerkatMachineInputVariant::RequestFiniteSwitchTurn,
1591            Self::RequestUntilChangedSwitchTurn => {
1592                MeerkatMachineInputVariant::RequestUntilChangedSwitchTurn
1593            }
1594            Self::AdmitModelRoutingAssistantTurn => {
1595                MeerkatMachineInputVariant::AdmitModelRoutingAssistantTurn
1596            }
1597            Self::BeginImageOperation => MeerkatMachineInputVariant::BeginImageOperation,
1598            Self::DenyImageOperationPlan => MeerkatMachineInputVariant::DenyImageOperationPlan,
1599            Self::ActivateImageOperationOverride => {
1600                MeerkatMachineInputVariant::ActivateImageOperationOverride
1601            }
1602            Self::ClassifyImageOperationTerminal => {
1603                MeerkatMachineInputVariant::ClassifyImageOperationTerminal
1604            }
1605            Self::CompleteImageOperation => MeerkatMachineInputVariant::CompleteImageOperation,
1606            Self::RestoreImageOperationOverride => {
1607                MeerkatMachineInputVariant::RestoreImageOperationOverride
1608            }
1609            Self::LoadBoundaryReceipt => MeerkatMachineInputVariant::LoadBoundaryReceipt,
1610            Self::AcceptWithCompletion => MeerkatMachineInputVariant::AcceptWithCompletion,
1611            Self::AcceptWithoutWake => MeerkatMachineInputVariant::AcceptWithoutWake,
1612        }
1613    }
1614
1615    #[must_use]
1616    pub const fn as_str(self) -> &'static str {
1617        match self {
1618            Self::RegisterSession => "RegisterSession",
1619            Self::UnregisterSession => "UnregisterSession",
1620            Self::EnsureSessionWithExecutor => "EnsureSessionWithExecutor",
1621            Self::SetSilentIntents => "SetSilentIntents",
1622            Self::CancelAfterBoundary => "CancelAfterBoundary",
1623            Self::StopRuntimeExecutor => "StopRuntimeExecutor",
1624            Self::ContainsSession => "ContainsSession",
1625            Self::SessionHasExecutor => "SessionHasExecutor",
1626            Self::SessionHasComms => "SessionHasComms",
1627            Self::OpsLifecycleRegistry => "OpsLifecycleRegistry",
1628            Self::PrepareBindings => "PrepareBindings",
1629            Self::InputState => "InputState",
1630            Self::ListActiveInputs => "ListActiveInputs",
1631            Self::ReconfigureSessionLlmIdentity => "ReconfigureSessionLlmIdentity",
1632            Self::StagePersistentFilter => "StagePersistentFilter",
1633            Self::RequestDeferredTools => "RequestDeferredTools",
1634            Self::PublishCommittedVisibleSet => "PublishCommittedVisibleSet",
1635            Self::SetPeerIngressContext => "SetPeerIngressContext",
1636            Self::NotifyDrainExited => "NotifyDrainExited",
1637            Self::AbortAll => "AbortAll",
1638            Self::Abort => "Abort",
1639            Self::Wait => "Wait",
1640            Self::Ingest => "Ingest",
1641            Self::PublishEvent => "PublishEvent",
1642            Self::Retire => "Retire",
1643            Self::Recycle => "Recycle",
1644            Self::Reset => "Reset",
1645            Self::Recover => "Recover",
1646            Self::Destroy => "Destroy",
1647            Self::RuntimeState => "RuntimeState",
1648            Self::ModelRoutingStatus => "ModelRoutingStatus",
1649            Self::SetModelRoutingBaseline => "SetModelRoutingBaseline",
1650            Self::RequestFiniteSwitchTurn => "RequestFiniteSwitchTurn",
1651            Self::RequestUntilChangedSwitchTurn => "RequestUntilChangedSwitchTurn",
1652            Self::AdmitModelRoutingAssistantTurn => "AdmitModelRoutingAssistantTurn",
1653            Self::BeginImageOperation => "BeginImageOperation",
1654            Self::DenyImageOperationPlan => "DenyImageOperationPlan",
1655            Self::ActivateImageOperationOverride => "ActivateImageOperationOverride",
1656            Self::ClassifyImageOperationTerminal => "ClassifyImageOperationTerminal",
1657            Self::CompleteImageOperation => "CompleteImageOperation",
1658            Self::RestoreImageOperationOverride => "RestoreImageOperationOverride",
1659            Self::LoadBoundaryReceipt => "LoadBoundaryReceipt",
1660            Self::AcceptWithCompletion => "AcceptWithCompletion",
1661            Self::AcceptWithoutWake => "AcceptWithoutWake",
1662        }
1663    }
1664}
1665
1666impl MeerkatMachineCommandVariant {
1667    #[must_use]
1668    pub const fn catalog_input(self) -> Option<MeerkatMachineCatalogInput> {
1669        match self {
1670            Self::ConfigureModelRoutingBaseline
1671            | Self::RequestSwitchTurn
1672            | Self::ResolvedSessionLlmCapabilities
1673            | Self::SessionModelRoutingStatus
1674            | Self::PrepareLocalSessionBindings => None,
1675            Self::RegisterSession => Some(MeerkatMachineCatalogInput::RegisterSession),
1676            Self::UnregisterSession => Some(MeerkatMachineCatalogInput::UnregisterSession),
1677            Self::EnsureSessionWithExecutor => {
1678                Some(MeerkatMachineCatalogInput::EnsureSessionWithExecutor)
1679            }
1680            Self::SetSilentIntents => Some(MeerkatMachineCatalogInput::SetSilentIntents),
1681            Self::CancelAfterBoundary => Some(MeerkatMachineCatalogInput::CancelAfterBoundary),
1682            Self::StopRuntimeExecutor => Some(MeerkatMachineCatalogInput::StopRuntimeExecutor),
1683            Self::ContainsSession => Some(MeerkatMachineCatalogInput::ContainsSession),
1684            Self::SessionHasExecutor => Some(MeerkatMachineCatalogInput::SessionHasExecutor),
1685            Self::SessionHasComms => Some(MeerkatMachineCatalogInput::SessionHasComms),
1686            Self::OpsLifecycleRegistry => Some(MeerkatMachineCatalogInput::OpsLifecycleRegistry),
1687            Self::PrepareBindings => Some(MeerkatMachineCatalogInput::PrepareBindings),
1688            Self::InputState => Some(MeerkatMachineCatalogInput::InputState),
1689            // Same machine-owned read route as `InputState` (the key
1690            // resolves through the generated admission map first).
1691            Self::InputStateByIdempotencyKey => Some(MeerkatMachineCatalogInput::InputState),
1692            // Terminal-status queries are the same machine-owned read route
1693            // as `InputState`: they evaluate the identical DSL-owned seed
1694            // facts (live snapshot or their durable store witnesses).
1695            Self::InteractionTerminalStatus => Some(MeerkatMachineCatalogInput::InputState),
1696            Self::RunTerminalStatus => Some(MeerkatMachineCatalogInput::InputState),
1697            Self::ListActiveInputs => Some(MeerkatMachineCatalogInput::ListActiveInputs),
1698            Self::ReconfigureSessionLlmIdentity => {
1699                Some(MeerkatMachineCatalogInput::ReconfigureSessionLlmIdentity)
1700            }
1701            Self::StagePersistentFilter => Some(MeerkatMachineCatalogInput::StagePersistentFilter),
1702            Self::RequestDeferredTools => Some(MeerkatMachineCatalogInput::RequestDeferredTools),
1703            Self::PublishCommittedVisibleSet => {
1704                Some(MeerkatMachineCatalogInput::PublishCommittedVisibleSet)
1705            }
1706            Self::SetPeerIngressContext => Some(MeerkatMachineCatalogInput::SetPeerIngressContext),
1707            Self::NotifyDrainExited => Some(MeerkatMachineCatalogInput::NotifyDrainExited),
1708            Self::AbortAll => Some(MeerkatMachineCatalogInput::AbortAll),
1709            Self::Abort => Some(MeerkatMachineCatalogInput::Abort),
1710            Self::Wait => Some(MeerkatMachineCatalogInput::Wait),
1711            Self::Ingest => Some(MeerkatMachineCatalogInput::Ingest),
1712            Self::PublishEvent => Some(MeerkatMachineCatalogInput::PublishEvent),
1713            Self::Retire => Some(MeerkatMachineCatalogInput::Retire),
1714            Self::Recycle => Some(MeerkatMachineCatalogInput::Recycle),
1715            Self::Reset => Some(MeerkatMachineCatalogInput::Reset),
1716            Self::Recover => Some(MeerkatMachineCatalogInput::Recover),
1717            Self::Destroy => Some(MeerkatMachineCatalogInput::Destroy),
1718            Self::RuntimeState => Some(MeerkatMachineCatalogInput::RuntimeState),
1719            Self::AdmitModelRoutingAssistantTurn => {
1720                Some(MeerkatMachineCatalogInput::AdmitModelRoutingAssistantTurn)
1721            }
1722            Self::BeginImageOperation => Some(MeerkatMachineCatalogInput::BeginImageOperation),
1723            Self::DenyImageOperationPlan => {
1724                Some(MeerkatMachineCatalogInput::DenyImageOperationPlan)
1725            }
1726            Self::ActivateImageOperationOverride => {
1727                Some(MeerkatMachineCatalogInput::ActivateImageOperationOverride)
1728            }
1729            Self::ClassifyImageOperationTerminal => {
1730                Some(MeerkatMachineCatalogInput::ClassifyImageOperationTerminal)
1731            }
1732            Self::CompleteImageOperation => {
1733                Some(MeerkatMachineCatalogInput::CompleteImageOperation)
1734            }
1735            Self::RestoreImageOperationOverride => {
1736                Some(MeerkatMachineCatalogInput::RestoreImageOperationOverride)
1737            }
1738            Self::LoadBoundaryReceipt => Some(MeerkatMachineCatalogInput::LoadBoundaryReceipt),
1739            Self::AcceptWithCompletion => Some(MeerkatMachineCatalogInput::AcceptWithCompletion),
1740            Self::AcceptWithoutWake => Some(MeerkatMachineCatalogInput::AcceptWithoutWake),
1741        }
1742    }
1743}
1744
1745#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1746pub enum MeerkatMachineShellMechanicReason {
1747    ModelRoutingShellConfiguration,
1748    TurnControlOverlayRequest,
1749    RealtimeTransportObservation,
1750    SessionModelRoutingObservation,
1751    LocalSessionBindingBootstrap,
1752}
1753
1754#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1755pub struct MeerkatMachineCommandClassificationRecord {
1756    pub command: MeerkatMachineCommandVariant,
1757    pub classification: MeerkatMachineCommandClassification,
1758}
1759
1760#[doc(hidden)]
1761#[must_use]
1762pub fn canonical_meerkat_machine_command_classifications()
1763-> Vec<MeerkatMachineCommandClassificationRecord> {
1764    MeerkatMachineCommand::command_variant_manifest()
1765        .iter()
1766        .copied()
1767        .map(|variant| MeerkatMachineCommandClassificationRecord {
1768            command: variant,
1769            classification: meerkat_machine_command_classification(variant),
1770        })
1771        .collect()
1772}
1773
1774const fn meerkat_machine_command_classification(
1775    variant: MeerkatMachineCommandVariant,
1776) -> MeerkatMachineCommandClassification {
1777    match variant {
1778        MeerkatMachineCommandVariant::ConfigureModelRoutingBaseline => {
1779            MeerkatMachineCommandClassification::CatalogInput(
1780                MeerkatMachineCatalogInput::SetModelRoutingBaseline,
1781            )
1782        }
1783        MeerkatMachineCommandVariant::RequestSwitchTurn => {
1784            MeerkatMachineCommandClassification::CatalogInputs(&[
1785                MeerkatMachineCatalogInput::RequestFiniteSwitchTurn,
1786                MeerkatMachineCatalogInput::RequestUntilChangedSwitchTurn,
1787            ])
1788        }
1789        MeerkatMachineCommandVariant::ResolvedSessionLlmCapabilities => {
1790            MeerkatMachineCommandClassification::ShellMechanic(
1791                MeerkatMachineShellMechanicReason::SessionModelRoutingObservation,
1792            )
1793        }
1794        MeerkatMachineCommandVariant::SessionModelRoutingStatus => {
1795            MeerkatMachineCommandClassification::CatalogInput(
1796                MeerkatMachineCatalogInput::ModelRoutingStatus,
1797            )
1798        }
1799        MeerkatMachineCommandVariant::PrepareLocalSessionBindings => {
1800            MeerkatMachineCommandClassification::ShellMechanic(
1801                MeerkatMachineShellMechanicReason::LocalSessionBindingBootstrap,
1802            )
1803        }
1804        MeerkatMachineCommandVariant::RegisterSession => {
1805            MeerkatMachineCommandClassification::CatalogInput(
1806                MeerkatMachineCatalogInput::RegisterSession,
1807            )
1808        }
1809        MeerkatMachineCommandVariant::UnregisterSession => {
1810            MeerkatMachineCommandClassification::CatalogInput(
1811                MeerkatMachineCatalogInput::UnregisterSession,
1812            )
1813        }
1814        MeerkatMachineCommandVariant::EnsureSessionWithExecutor => {
1815            MeerkatMachineCommandClassification::CatalogInput(
1816                MeerkatMachineCatalogInput::EnsureSessionWithExecutor,
1817            )
1818        }
1819        MeerkatMachineCommandVariant::SetSilentIntents => {
1820            MeerkatMachineCommandClassification::CatalogInput(
1821                MeerkatMachineCatalogInput::SetSilentIntents,
1822            )
1823        }
1824        MeerkatMachineCommandVariant::CancelAfterBoundary => {
1825            MeerkatMachineCommandClassification::CatalogInput(
1826                MeerkatMachineCatalogInput::CancelAfterBoundary,
1827            )
1828        }
1829        MeerkatMachineCommandVariant::StopRuntimeExecutor => {
1830            MeerkatMachineCommandClassification::CatalogInput(
1831                MeerkatMachineCatalogInput::StopRuntimeExecutor,
1832            )
1833        }
1834        MeerkatMachineCommandVariant::ContainsSession => {
1835            MeerkatMachineCommandClassification::CatalogInput(
1836                MeerkatMachineCatalogInput::ContainsSession,
1837            )
1838        }
1839        MeerkatMachineCommandVariant::SessionHasExecutor => {
1840            MeerkatMachineCommandClassification::CatalogInput(
1841                MeerkatMachineCatalogInput::SessionHasExecutor,
1842            )
1843        }
1844        MeerkatMachineCommandVariant::SessionHasComms => {
1845            MeerkatMachineCommandClassification::CatalogInput(
1846                MeerkatMachineCatalogInput::SessionHasComms,
1847            )
1848        }
1849        MeerkatMachineCommandVariant::OpsLifecycleRegistry => {
1850            MeerkatMachineCommandClassification::CatalogInput(
1851                MeerkatMachineCatalogInput::OpsLifecycleRegistry,
1852            )
1853        }
1854        MeerkatMachineCommandVariant::PrepareBindings => {
1855            MeerkatMachineCommandClassification::CatalogInput(
1856                MeerkatMachineCatalogInput::PrepareBindings,
1857            )
1858        }
1859        MeerkatMachineCommandVariant::InputState => {
1860            MeerkatMachineCommandClassification::CatalogInput(
1861                MeerkatMachineCatalogInput::InputState,
1862            )
1863        }
1864        // Same machine-owned read route as `InputState`: the idempotency
1865        // key resolves through the generated admission map, then the stored
1866        // input state is read identically.
1867        MeerkatMachineCommandVariant::InputStateByIdempotencyKey => {
1868            MeerkatMachineCommandClassification::CatalogInput(
1869                MeerkatMachineCatalogInput::InputState,
1870            )
1871        }
1872        // Terminal-status reads evaluate the same DSL-owned seed facts as
1873        // `InputState` (live snapshot for registered sessions, durable store
1874        // witnesses otherwise) — same machine-owned read route.
1875        MeerkatMachineCommandVariant::InteractionTerminalStatus
1876        | MeerkatMachineCommandVariant::RunTerminalStatus => {
1877            MeerkatMachineCommandClassification::CatalogInput(
1878                MeerkatMachineCatalogInput::InputState,
1879            )
1880        }
1881        MeerkatMachineCommandVariant::ListActiveInputs => {
1882            MeerkatMachineCommandClassification::CatalogInput(
1883                MeerkatMachineCatalogInput::ListActiveInputs,
1884            )
1885        }
1886        MeerkatMachineCommandVariant::ReconfigureSessionLlmIdentity => {
1887            MeerkatMachineCommandClassification::CatalogInput(
1888                MeerkatMachineCatalogInput::ReconfigureSessionLlmIdentity,
1889            )
1890        }
1891        MeerkatMachineCommandVariant::StagePersistentFilter => {
1892            MeerkatMachineCommandClassification::CatalogInput(
1893                MeerkatMachineCatalogInput::StagePersistentFilter,
1894            )
1895        }
1896        MeerkatMachineCommandVariant::RequestDeferredTools => {
1897            MeerkatMachineCommandClassification::CatalogInput(
1898                MeerkatMachineCatalogInput::RequestDeferredTools,
1899            )
1900        }
1901        MeerkatMachineCommandVariant::PublishCommittedVisibleSet => {
1902            MeerkatMachineCommandClassification::CatalogInput(
1903                MeerkatMachineCatalogInput::PublishCommittedVisibleSet,
1904            )
1905        }
1906        MeerkatMachineCommandVariant::SetPeerIngressContext => {
1907            MeerkatMachineCommandClassification::CatalogInput(
1908                MeerkatMachineCatalogInput::SetPeerIngressContext,
1909            )
1910        }
1911        MeerkatMachineCommandVariant::NotifyDrainExited => {
1912            MeerkatMachineCommandClassification::CatalogInput(
1913                MeerkatMachineCatalogInput::NotifyDrainExited,
1914            )
1915        }
1916        MeerkatMachineCommandVariant::AbortAll => {
1917            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::AbortAll)
1918        }
1919        MeerkatMachineCommandVariant::Abort => {
1920            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Abort)
1921        }
1922        MeerkatMachineCommandVariant::Wait => {
1923            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Wait)
1924        }
1925        MeerkatMachineCommandVariant::Ingest => {
1926            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Ingest)
1927        }
1928        MeerkatMachineCommandVariant::PublishEvent => {
1929            MeerkatMachineCommandClassification::CatalogInput(
1930                MeerkatMachineCatalogInput::PublishEvent,
1931            )
1932        }
1933        MeerkatMachineCommandVariant::Retire => {
1934            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Retire)
1935        }
1936        MeerkatMachineCommandVariant::Recycle => {
1937            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Recycle)
1938        }
1939        MeerkatMachineCommandVariant::Reset => {
1940            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Reset)
1941        }
1942        MeerkatMachineCommandVariant::Recover => {
1943            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Recover)
1944        }
1945        MeerkatMachineCommandVariant::Destroy => {
1946            MeerkatMachineCommandClassification::CatalogInput(MeerkatMachineCatalogInput::Destroy)
1947        }
1948        MeerkatMachineCommandVariant::RuntimeState => {
1949            MeerkatMachineCommandClassification::CatalogInput(
1950                MeerkatMachineCatalogInput::RuntimeState,
1951            )
1952        }
1953        MeerkatMachineCommandVariant::AdmitModelRoutingAssistantTurn => {
1954            MeerkatMachineCommandClassification::CatalogInput(
1955                MeerkatMachineCatalogInput::AdmitModelRoutingAssistantTurn,
1956            )
1957        }
1958        MeerkatMachineCommandVariant::BeginImageOperation => {
1959            MeerkatMachineCommandClassification::CatalogInput(
1960                MeerkatMachineCatalogInput::BeginImageOperation,
1961            )
1962        }
1963        MeerkatMachineCommandVariant::DenyImageOperationPlan => {
1964            MeerkatMachineCommandClassification::CatalogInput(
1965                MeerkatMachineCatalogInput::DenyImageOperationPlan,
1966            )
1967        }
1968        MeerkatMachineCommandVariant::ActivateImageOperationOverride => {
1969            MeerkatMachineCommandClassification::CatalogInput(
1970                MeerkatMachineCatalogInput::ActivateImageOperationOverride,
1971            )
1972        }
1973        MeerkatMachineCommandVariant::ClassifyImageOperationTerminal => {
1974            MeerkatMachineCommandClassification::CatalogInput(
1975                MeerkatMachineCatalogInput::ClassifyImageOperationTerminal,
1976            )
1977        }
1978        MeerkatMachineCommandVariant::CompleteImageOperation => {
1979            MeerkatMachineCommandClassification::CatalogInput(
1980                MeerkatMachineCatalogInput::CompleteImageOperation,
1981            )
1982        }
1983        MeerkatMachineCommandVariant::RestoreImageOperationOverride => {
1984            MeerkatMachineCommandClassification::CatalogInput(
1985                MeerkatMachineCatalogInput::RestoreImageOperationOverride,
1986            )
1987        }
1988        MeerkatMachineCommandVariant::LoadBoundaryReceipt => {
1989            MeerkatMachineCommandClassification::CatalogInput(
1990                MeerkatMachineCatalogInput::LoadBoundaryReceipt,
1991            )
1992        }
1993        MeerkatMachineCommandVariant::AcceptWithCompletion => {
1994            MeerkatMachineCommandClassification::CatalogInput(
1995                MeerkatMachineCatalogInput::AcceptWithCompletion,
1996            )
1997        }
1998        MeerkatMachineCommandVariant::AcceptWithoutWake => {
1999            MeerkatMachineCommandClassification::CatalogInput(
2000                MeerkatMachineCatalogInput::AcceptWithoutWake,
2001            )
2002        }
2003    }
2004}
2005
2006/// Snapshot of completion waiters registered for one input.
2007///
2008/// This is a supporting-carrier view, not canonical semantic truth.
2009#[derive(Debug, Clone, PartialEq, Eq)]
2010pub struct MeerkatCompletionWaiterSnapshot {
2011    pub input_id: InputId,
2012    pub waiter_count: usize,
2013}
2014
2015/// Snapshot of the runtime completion waiter carrier.
2016///
2017/// Completion waiters are supporting carrier state rather than primary
2018/// authority, but they remain useful for diagnostics and recovery inspection.
2019#[derive(Debug, Clone, PartialEq, Eq)]
2020pub struct MeerkatCompletionWaitersSnapshot {
2021    pub input_count: usize,
2022    pub waiter_count: usize,
2023    pub waiting_inputs: Vec<MeerkatCompletionWaiterSnapshot>,
2024}
2025
2026/// Runtime driver flavor for a registered Meerkat session entry.
2027#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2028pub enum MeerkatDriverKind {
2029    Ephemeral,
2030    Persistent,
2031}
2032
2033/// Snapshot of the hidden runtime epoch cursor state.
2034#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2035pub struct MeerkatCursorSnapshot {
2036    pub agent_applied_cursor: u64,
2037    pub runtime_observed_seq: u64,
2038    pub runtime_last_injected_seq: u64,
2039}
2040
2041/// Snapshot of the hidden runtime binding for one Meerkat session.
2042#[derive(Debug, Clone)]
2043pub struct MeerkatBindingSnapshot {
2044    pub session_id: SessionId,
2045    pub runtime_id: LogicalRuntimeId,
2046    pub driver_kind: MeerkatDriverKind,
2047    pub driver_present: bool,
2048    pub completions_present: bool,
2049    pub ops_registry_present: bool,
2050    pub epoch_id: RuntimeEpochId,
2051    pub cursor_state: MeerkatCursorSnapshot,
2052}
2053
2054/// Snapshot of runtime control-plane truth for one session.
2055#[derive(Debug, Clone)]
2056pub struct MeerkatControlSnapshot {
2057    pub phase: RuntimeState,
2058    pub current_run_id: Option<RunId>,
2059    pub pre_run_phase: Option<RuntimeState>,
2060}
2061
2062/// Snapshot of one admitted runtime input.
2063#[derive(Debug, Clone, PartialEq, Eq)]
2064pub struct MeerkatAdmittedInputSnapshot {
2065    pub input_id: InputId,
2066    pub content_shape: Option<ContentShape>,
2067    pub request_id: Option<RequestId>,
2068    pub reservation_key: Option<ReservationKey>,
2069    pub handling_mode: Option<HandlingMode>,
2070    /// #338: machine-owned per-input live-interrupt verdict, carried from the
2071    /// admission `RuntimeInputSemantics`. The live-projection consumer reads
2072    /// this typed fact instead of re-scanning `handling_mode == Steer`.
2073    pub live_interrupt_required: bool,
2074    pub lifecycle: Option<InputLifecycleState>,
2075    pub terminal_outcome: Option<InputTerminalOutcome>,
2076    pub last_run_id: Option<RunId>,
2077    pub last_boundary_sequence: Option<u64>,
2078    pub is_prompt: bool,
2079}
2080
2081/// Snapshot of runtime ingress truth for one session.
2082#[derive(Debug, Clone, PartialEq, Eq)]
2083pub struct MeerkatInputsSnapshot {
2084    pub admission_order: Vec<MeerkatAdmittedInputSnapshot>,
2085    pub queue: Vec<InputId>,
2086    pub steer_queue: Vec<InputId>,
2087    pub current_run_id: Option<RunId>,
2088    pub current_run_contributors: Vec<InputId>,
2089    pub post_admission_signal: String,
2090    pub silent_intent_overrides: Vec<String>,
2091}
2092
2093/// Lightweight lifecycle snapshot used by archive/retire cleanup.
2094///
2095/// This intentionally avoids the heavier diagnostic regions that are not
2096/// needed for archive decisions. Control and input fields still come from the
2097/// generated runtime authority/driver projections.
2098#[derive(Debug, Clone)]
2099pub struct MeerkatArchiveSnapshot {
2100    pub control: MeerkatControlSnapshot,
2101    pub queue: Vec<InputId>,
2102    pub steer_queue: Vec<InputId>,
2103    pub completion_waiters: MeerkatCompletionWaitersSnapshot,
2104}
2105
2106/// Snapshot of the canonical input-ledger carrier for one session.
2107///
2108/// These counts sit below the top-level Meerkat phase machine, but they drive
2109/// the exact values returned by control-plane reports such as
2110/// `DestroyReport.inputs_abandoned`.
2111#[derive(Debug, Clone)]
2112pub struct MeerkatLedgerSnapshot {
2113    pub input_count: usize,
2114    pub non_terminal_count: usize,
2115    pub accepted_count: usize,
2116    pub queued_count: usize,
2117    pub staged_count: usize,
2118    pub applied_count: usize,
2119    pub applied_pending_consumption_count: usize,
2120    pub consumed_count: usize,
2121    pub superseded_count: usize,
2122    pub coalesced_count: usize,
2123    pub abandoned_count: usize,
2124}
2125
2126/// Snapshot of runtime-owned async operation truth for one session.
2127#[derive(Debug, Clone)]
2128pub struct MeerkatOpsSnapshot {
2129    pub operation_count: usize,
2130    pub active_count: usize,
2131    pub wait_request_id: Option<WaitRequestId>,
2132    pub pending_wait_present: bool,
2133    pub pending_wait_request_id: Option<WaitRequestId>,
2134    pub wait_operation_ids: Vec<OperationId>,
2135    pub operations: Vec<OperationLifecycleSnapshot>,
2136}
2137
2138/// Diagnostic comms-drain projection for one session.
2139///
2140/// `phase` and `mode` are read from generated MeerkatMachine authority; the
2141/// handle flag is shell task mechanics only.
2142#[derive(Debug, Clone)]
2143pub struct MeerkatDrainSnapshot {
2144    pub slot_present: bool,
2145    pub phase: Option<CommsDrainPhase>,
2146    pub mode: Option<CommsDrainMode>,
2147    pub handle_present: bool,
2148}
2149
2150/// Schema-aligned projection of the Meerkat formal state derived from the
2151/// live runtime.
2152///
2153/// This stays diagnostic and intentionally stringifies field values so the
2154/// parity harness can compare full field vectors even where the runtime does
2155/// not expose a first-class Rust type for every formal field.
2156#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2157pub struct MeerkatFormalStateProjection {
2158    /// Formal fields that have a live runtime-backed projection today.
2159    pub available_fields: BTreeMap<String, String>,
2160    /// Formal fields that still have no canonical runtime source.
2161    pub unavailable_fields: Vec<String>,
2162}
2163
2164/// Diagnostic snapshot of the current Meerkat runtime spine.
2165///
2166/// This is an observational scaffold over the existing runtime-owned Meerkat
2167/// regions. It is intentionally not the final MeerkatMachine reducer.
2168#[derive(Debug, Clone)]
2169pub struct MeerkatMachineSpineSnapshot {
2170    pub binding: MeerkatBindingSnapshot,
2171    pub control: MeerkatControlSnapshot,
2172    pub inputs: MeerkatInputsSnapshot,
2173    pub ledger: MeerkatLedgerSnapshot,
2174    pub completion_waiters: MeerkatCompletionWaitersSnapshot,
2175    pub ops: MeerkatOpsSnapshot,
2176    pub drain: MeerkatDrainSnapshot,
2177    pub formal_state: MeerkatFormalStateProjection,
2178}
2179
2180impl MeerkatMachineSpineSnapshot {
2181    /// Validate TLA+ structural invariants against the current spine snapshot.
2182    ///
2183    /// Returns `Ok(())` if all invariants hold, or `Err(violations)` with a
2184    /// list of human-readable violation descriptions. This is release-mode
2185    /// validation — not `debug_assert`.
2186    pub fn validate_spine_invariants(&self) -> Result<(), Vec<String>> {
2187        let mut violations = Vec::new();
2188
2189        // --- Control/binding invariants ---
2190
2191        // RunningHasActiveRunInvariant: Running => HasActiveRun
2192        if self.control.phase == RuntimeState::Running && self.control.current_run_id.is_none() {
2193            violations
2194                .push("RunningHasActiveRunInvariant: phase is Running but no active run_id".into());
2195        }
2196
2197        // ActiveRunPhaseInvariant: HasActiveRun => phase in {Running, Retired}
2198        if self.control.current_run_id.is_some()
2199            && !matches!(
2200                self.control.phase,
2201                RuntimeState::Running | RuntimeState::Retired
2202            )
2203        {
2204            violations.push(format!(
2205                "ActiveRunPhaseInvariant: active run_id present but phase is {:?}",
2206                self.control.phase
2207            ));
2208        }
2209
2210        // DestroyedShapeInvariant: Destroyed => empty queues, no waiting inputs
2211        if self.control.phase == RuntimeState::Destroyed {
2212            if !self.inputs.queue.is_empty() {
2213                violations.push("DestroyedShapeInvariant: Destroyed but queue is non-empty".into());
2214            }
2215            if !self.inputs.steer_queue.is_empty() {
2216                violations
2217                    .push("DestroyedShapeInvariant: Destroyed but steer_queue is non-empty".into());
2218            }
2219            if self.completion_waiters.input_count > 0 {
2220                violations.push(
2221                    "DestroyedShapeInvariant: Destroyed but completion waiters remain".into(),
2222                );
2223            }
2224        }
2225
2226        // --- Input invariants ---
2227
2228        // QueueSteerDisjointInvariant
2229        let queue_set: std::collections::HashSet<_> = self.inputs.queue.iter().collect();
2230        let steer_set: std::collections::HashSet<_> = self.inputs.steer_queue.iter().collect();
2231        if !queue_set.is_disjoint(&steer_set) {
2232            violations
2233                .push("QueueSteerDisjointInvariant: queue and steer_queue share entries".into());
2234        }
2235
2236        // QueueHandlingInvariant: all queue entries must have handling_mode=Queue, lifecycle=Queued
2237        for qid in &self.inputs.queue {
2238            if let Some(snap) = self
2239                .inputs
2240                .admission_order
2241                .iter()
2242                .find(|a| &a.input_id == qid)
2243            {
2244                if snap.handling_mode != Some(HandlingMode::Queue) {
2245                    violations.push(format!(
2246                        "QueueHandlingInvariant: queue entry {qid} has handling_mode {:?}",
2247                        snap.handling_mode
2248                    ));
2249                }
2250                if snap.lifecycle != Some(InputLifecycleState::Queued) {
2251                    violations.push(format!(
2252                        "QueueHandlingInvariant: queue entry {qid} has lifecycle {:?}",
2253                        snap.lifecycle
2254                    ));
2255                }
2256            }
2257        }
2258
2259        // SteerHandlingInvariant: all steer_queue entries must have handling_mode=Steer, lifecycle=Queued
2260        for sid in &self.inputs.steer_queue {
2261            if let Some(snap) = self
2262                .inputs
2263                .admission_order
2264                .iter()
2265                .find(|a| &a.input_id == sid)
2266            {
2267                if snap.handling_mode != Some(HandlingMode::Steer) {
2268                    violations.push(format!(
2269                        "SteerHandlingInvariant: steer_queue entry {sid} has handling_mode {:?}",
2270                        snap.handling_mode
2271                    ));
2272                }
2273                if snap.lifecycle != Some(InputLifecycleState::Queued) {
2274                    violations.push(format!(
2275                        "SteerHandlingInvariant: steer_queue entry {sid} has lifecycle {:?}",
2276                        snap.lifecycle
2277                    ));
2278                }
2279            }
2280        }
2281
2282        // ContributorLifecycleInvariant: all current_run_contributors must
2283        // still belong to the active run lifecycle slice.
2284        for cid in &self.inputs.current_run_contributors {
2285            if let Some(snap) = self
2286                .inputs
2287                .admission_order
2288                .iter()
2289                .find(|a| &a.input_id == cid)
2290                && !matches!(
2291                    snap.lifecycle,
2292                    Some(
2293                        InputLifecycleState::Staged
2294                            | InputLifecycleState::Applied
2295                            | InputLifecycleState::AppliedPendingConsumption
2296                    )
2297                )
2298            {
2299                violations.push(format!(
2300                    "ContributorLifecycleInvariant: contributor {cid} has lifecycle {:?}",
2301                    snap.lifecycle
2302                ));
2303            }
2304        }
2305
2306        // TerminalInputsNotQueuedInvariant: terminal inputs must not be in queue or steer_queue
2307        for snap in &self.inputs.admission_order {
2308            if snap.terminal_outcome.is_some() {
2309                if queue_set.contains(&snap.input_id) {
2310                    violations.push(format!(
2311                        "TerminalInputsNotQueuedInvariant: terminal input {} in queue",
2312                        snap.input_id
2313                    ));
2314                }
2315                if steer_set.contains(&snap.input_id) {
2316                    violations.push(format!(
2317                        "TerminalInputsNotQueuedInvariant: terminal input {} in steer_queue",
2318                        snap.input_id
2319                    ));
2320                }
2321            }
2322        }
2323
2324        // CurrentRunContributorsInvariant: HasActiveRun => contributors non-empty
2325        if self.control.current_run_id.is_some() && self.inputs.current_run_contributors.is_empty()
2326        {
2327            violations
2328                .push("CurrentRunContributorsInvariant: active run but no contributors".into());
2329        }
2330
2331        // ContributorRunIdentityInvariant: when control owns an active run,
2332        // every current contributor must point at that same run in the
2333        // ingress bookkeeping.
2334        if let Some(control_run_id) = &self.control.current_run_id {
2335            for cid in &self.inputs.current_run_contributors {
2336                if let Some(snap) = self
2337                    .inputs
2338                    .admission_order
2339                    .iter()
2340                    .find(|a| &a.input_id == cid)
2341                    && snap.last_run_id.as_ref() != Some(control_run_id)
2342                {
2343                    violations.push(format!(
2344                        "ContributorRunIdentityInvariant: contributor {cid} has last_run_id {:?}, expected {:?}",
2345                        snap.last_run_id, control_run_id
2346                    ));
2347                }
2348            }
2349        }
2350
2351        // --- Ops invariants ---
2352
2353        // WaitAllAlignmentInvariant
2354        let wait_active = self.ops.wait_request_id.is_some();
2355        if wait_active && self.ops.wait_operation_ids.is_empty() {
2356            violations
2357                .push("WaitAllAlignmentInvariant: wait_active but no wait_operation_ids".into());
2358        }
2359        if !wait_active && !self.ops.wait_operation_ids.is_empty() {
2360            violations.push(
2361                "WaitAllAlignmentInvariant: wait_operation_ids present but no wait_request_id"
2362                    .into(),
2363            );
2364        }
2365
2366        // --- Drain invariants ---
2367
2368        // DrainModeInvariant: drain.phase != Inactive => mode is set and not Disabled
2369        if let Some(phase) = self.drain.phase
2370            && phase != CommsDrainPhase::Inactive
2371            && self.drain.mode.is_none()
2372        {
2373            violations.push("DrainModeInvariant: drain.phase is active but mode is None".into());
2374        }
2375
2376        if violations.is_empty() {
2377            Ok(())
2378        } else {
2379            Err(violations)
2380        }
2381    }
2382}