Skip to main content

meerkat_runtime/meerkat_machine/
dsl.rs

1//! MeerkatMachine DSL definition with real bridging types.
2use meerkat_machine_dsl::machine;
3use meerkat_machine_schema::catalog::dsl::OptionValueExt;
4
5// ---------------------------------------------------------------------------
6// Bridging types
7// ---------------------------------------------------------------------------
8
9#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
10pub struct SessionId(pub String);
11
12impl<T: Into<String>> From<T> for SessionId {
13    fn from(s: T) -> Self {
14        Self(s.into())
15    }
16}
17
18impl SessionId {
19    pub fn from_domain(id: &meerkat_core::types::SessionId) -> Self {
20        Self(id.to_string())
21    }
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
25pub struct AgentRuntimeId(pub String);
26
27impl<T: Into<String>> From<T> for AgentRuntimeId {
28    fn from(s: T) -> Self {
29        Self(s.into())
30    }
31}
32
33impl AgentRuntimeId {
34    pub fn from_domain(id: &crate::identifiers::LogicalRuntimeId) -> Self {
35        Self(id.to_string())
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
40pub struct FenceToken(pub u64);
41
42impl From<u64> for FenceToken {
43    fn from(v: u64) -> Self {
44        Self(v)
45    }
46}
47
48impl FenceToken {
49    pub fn from_domain(value: u64) -> Self {
50        Self(value)
51    }
52}
53
54#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
55pub struct Generation(pub u64);
56
57impl From<u64> for Generation {
58    fn from(v: u64) -> Self {
59        Self(v)
60    }
61}
62
63impl Generation {
64    pub fn from_domain(value: u64) -> Self {
65        Self(value)
66    }
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
70pub struct RunId(pub String);
71
72impl<T: Into<String>> From<T> for RunId {
73    fn from(s: T) -> Self {
74        Self(s.into())
75    }
76}
77
78impl RunId {
79    pub fn from_domain(id: &meerkat_core::lifecycle::RunId) -> Self {
80        Self(id.to_string())
81    }
82}
83
84#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
85pub struct InputId(pub String);
86
87impl<T: Into<String>> From<T> for InputId {
88    fn from(s: T) -> Self {
89        Self(s.into())
90    }
91}
92
93impl InputId {
94    pub fn from_domain(id: &meerkat_core::lifecycle::InputId) -> Self {
95        Self(id.to_string())
96    }
97}
98
99#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
100pub struct WorkId(pub String);
101
102impl<T: Into<String>> From<T> for WorkId {
103    fn from(s: T) -> Self {
104        Self(s.into())
105    }
106}
107
108impl WorkId {
109    pub fn from_domain(id: &meerkat_core::lifecycle::InputId) -> Self {
110        Self(id.to_string())
111    }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
115pub struct OperationId(pub String);
116
117impl<T: Into<String>> From<T> for OperationId {
118    fn from(s: T) -> Self {
119        Self(s.into())
120    }
121}
122
123impl OperationId {
124    pub fn from_domain(id: &meerkat_core::ops::OperationId) -> Self {
125        Self::from(serde_json::to_string(id).unwrap_or_else(|_| "\"unknown\"".to_string()))
126    }
127}
128
129#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
130pub struct WaitRequestId(pub String);
131
132impl<T: Into<String>> From<T> for WaitRequestId {
133    fn from(s: T) -> Self {
134        Self(s.into())
135    }
136}
137
138impl WaitRequestId {
139    pub fn from_domain(id: &meerkat_core::lifecycle::WaitRequestId) -> Self {
140        Self(id.to_string())
141    }
142}
143
144/// Typed async-operation kind. Closed mirror of
145/// [`meerkat_core::ops_lifecycle::OperationKind`] — replaces the former
146/// newtype wrapper around an opaque JSON-encoded string. The DSL writes this
147/// variant directly on `RegisterOp` so guards on `PeerReadyOp`
148/// (`kind_is_mob_member_child`) can reason about the closed set without
149/// string parsing.
150#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
151pub enum OperationKind {
152    #[default]
153    MobMemberChild,
154    BackgroundToolOp,
155}
156
157impl From<meerkat_core::ops_lifecycle::OperationKind> for OperationKind {
158    fn from(kind: meerkat_core::ops_lifecycle::OperationKind) -> Self {
159        match kind {
160            meerkat_core::ops_lifecycle::OperationKind::MobMemberChild => Self::MobMemberChild,
161            meerkat_core::ops_lifecycle::OperationKind::BackgroundToolOp => Self::BackgroundToolOp,
162        }
163    }
164}
165
166impl From<OperationKind> for meerkat_core::ops_lifecycle::OperationKind {
167    fn from(kind: OperationKind) -> Self {
168        match kind {
169            OperationKind::MobMemberChild => Self::MobMemberChild,
170            OperationKind::BackgroundToolOp => Self::BackgroundToolOp,
171        }
172    }
173}
174
175impl OperationKind {
176    pub fn from_domain(kind: &meerkat_core::ops_lifecycle::OperationKind) -> Self {
177        Self::from(*kind)
178    }
179}
180
181/// Typed mirror of [`meerkat_core::Provider`] for use inside DSL bridging
182/// types. Closed 5-variant enum; the seam carries the discriminant directly
183/// rather than a JSON-encoded string.
184#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
185pub enum Provider {
186    #[default]
187    Anthropic,
188    OpenAI,
189    Gemini,
190    SelfHosted,
191    Other,
192}
193
194impl From<meerkat_core::provider::Provider> for Provider {
195    fn from(p: meerkat_core::provider::Provider) -> Self {
196        match p {
197            meerkat_core::provider::Provider::Anthropic => Self::Anthropic,
198            meerkat_core::provider::Provider::OpenAI => Self::OpenAI,
199            meerkat_core::provider::Provider::Gemini => Self::Gemini,
200            meerkat_core::provider::Provider::SelfHosted => Self::SelfHosted,
201            meerkat_core::provider::Provider::Other => Self::Other,
202        }
203    }
204}
205
206impl From<Provider> for meerkat_core::provider::Provider {
207    fn from(p: Provider) -> Self {
208        match p {
209            Provider::Anthropic => Self::Anthropic,
210            Provider::OpenAI => Self::OpenAI,
211            Provider::Gemini => Self::Gemini,
212            Provider::SelfHosted => Self::SelfHosted,
213            Provider::Other => Self::Other,
214        }
215    }
216}
217
218/// Typed mirror of [`meerkat_core::AuthBindingRef`] — structural string
219/// projection carrying the flat forms of `realm` / `binding` / `profile`
220/// with bidirectional `From`.
221///
222/// The DSL layer keeps string fields because this mirror is the
223/// DSL-layer identity carrier (used inside runtime-owned guards /
224/// transitions where slug validation has already happened at the
225/// boundary). Domain-side `AuthBindingRef` carries the typed atoms
226/// (`RealmId` / `BindingId` / `ProfileId`).
227#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
228pub struct AuthBindingRef {
229    pub realm_id: String,
230    pub binding_id: String,
231    pub profile_id: Option<String>,
232}
233
234impl From<&meerkat_core::AuthBindingRef> for AuthBindingRef {
235    fn from(r: &meerkat_core::AuthBindingRef) -> Self {
236        Self {
237            realm_id: r.realm.as_str().to_owned(),
238            binding_id: r.binding.as_str().to_owned(),
239            profile_id: r.profile.as_ref().map(|p| p.as_str().to_owned()),
240        }
241    }
242}
243
244/// Fallible conversion — DSL-layer flat strings may be slug-invalid
245/// (the DSL mirror intentionally accepts opaque strings to survive
246/// deserialization drift across schema versions), so lifting back to
247/// the typed-atom domain form may reject.
248impl TryFrom<AuthBindingRef> for meerkat_core::AuthBindingRef {
249    type Error = meerkat_core::IdentityError;
250
251    fn try_from(r: AuthBindingRef) -> Result<Self, Self::Error> {
252        Ok(Self {
253            realm: meerkat_core::RealmId::parse(&r.realm_id)?,
254            binding: meerkat_core::BindingId::parse(&r.binding_id)?,
255            profile: r
256                .profile_id
257                .as_deref()
258                .map(meerkat_core::ProfileId::parse)
259                .transpose()?,
260        })
261    }
262}
263
264/// Typed mirror of [`meerkat_core::SessionLlmIdentity`] — structural field
265/// projection with typed `Provider` and `AuthBindingRef` mirrors. The
266/// `provider_params` payload is a legitimately open-set `serde_json::Value`
267/// at the persistence boundary (arbitrary provider-specific options), so it
268/// rides on a stable JSON-serialization field inside the DSL — never parsed
269/// back as a discriminant inside any guard or transition.
270#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
271pub struct SessionLlmIdentity {
272    pub model: String,
273    pub provider: Provider,
274    pub self_hosted_server_id: Option<String>,
275    /// Stable JSON serialization of the open-set `provider_params` payload.
276    /// Carried as an opaque identity token; DSL guards never inspect its
277    /// content. Boundary-legitimate per the dogma round-4 brief's
278    /// "variable JSON payload" carve-out applied at field granularity.
279    pub provider_params_repr: Option<String>,
280    pub auth_binding: Option<AuthBindingRef>,
281}
282
283impl SessionLlmIdentity {
284    pub fn from_domain(id: &meerkat_core::SessionLlmIdentity) -> Self {
285        Self {
286            model: id.model.clone(),
287            provider: Provider::from(id.provider),
288            self_hosted_server_id: id.self_hosted_server_id.clone(),
289            provider_params_repr: id
290                .provider_params
291                .as_ref()
292                .map(|v| serde_json::to_string(v).unwrap_or_default()),
293            auth_binding: id.auth_binding.as_ref().map(AuthBindingRef::from),
294        }
295    }
296}
297
298/// Typed mirror of [`meerkat_core::SessionToolVisibilityState`] —
299/// structural projection using typed `ToolFilter` / `ToolVisibilityWitness`
300/// mirrors plus ordered name sets for deterministic Ord/Hash.
301#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
302pub struct SessionToolVisibilityState {
303    pub capability_base_filter: ToolFilter,
304    pub inherited_base_filter: ToolFilter,
305    pub active_filter: ToolFilter,
306    pub staged_filter: ToolFilter,
307    pub active_requested_deferred_names: std::collections::BTreeSet<String>,
308    pub staged_requested_deferred_names: std::collections::BTreeSet<String>,
309    pub active_revision: u64,
310    pub staged_revision: u64,
311    pub requested_witnesses: std::collections::BTreeMap<String, ToolVisibilityWitness>,
312    pub filter_witnesses: std::collections::BTreeMap<String, ToolVisibilityWitness>,
313}
314
315impl SessionToolVisibilityState {
316    pub fn from_domain(id: &meerkat_core::SessionToolVisibilityState) -> Self {
317        Self {
318            capability_base_filter: ToolFilter::from(&id.capability_base_filter),
319            inherited_base_filter: ToolFilter::from(&id.inherited_base_filter),
320            active_filter: ToolFilter::from(&id.active_filter),
321            staged_filter: ToolFilter::from(&id.staged_filter),
322            active_requested_deferred_names: id.active_requested_deferred_names.clone(),
323            staged_requested_deferred_names: id.staged_requested_deferred_names.clone(),
324            active_revision: id.active_revision,
325            staged_revision: id.staged_revision,
326            requested_witnesses: id
327                .requested_witnesses
328                .iter()
329                .map(|(k, w)| (k.clone(), ToolVisibilityWitness::from(w)))
330                .collect(),
331            filter_witnesses: id
332                .filter_witnesses
333                .iter()
334                .map(|(k, w)| (k.clone(), ToolVisibilityWitness::from(w)))
335                .collect(),
336        }
337    }
338}
339
340/// Typed mirror of
341/// [`crate::meerkat_machine_types::SessionLlmCapabilitySurface`] — structural
342/// projection of the boolean capability matrix plus optional call timeout.
343#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
344pub struct SessionLlmCapabilitySurface {
345    pub supports_temperature: bool,
346    pub supports_thinking: bool,
347    pub supports_reasoning: bool,
348    pub inline_video: bool,
349    pub vision: bool,
350    pub image_input: bool,
351    pub image_tool_results: bool,
352    pub supports_web_search: bool,
353    pub image_generation: bool,
354    pub realtime: bool,
355    pub call_timeout_secs: Option<u64>,
356}
357
358impl From<&crate::meerkat_machine_types::SessionLlmCapabilitySurface>
359    for SessionLlmCapabilitySurface
360{
361    fn from(s: &crate::meerkat_machine_types::SessionLlmCapabilitySurface) -> Self {
362        Self {
363            supports_temperature: s.supports_temperature,
364            supports_thinking: s.supports_thinking,
365            supports_reasoning: s.supports_reasoning,
366            inline_video: s.inline_video,
367            vision: s.vision,
368            image_input: s.image_input,
369            image_tool_results: s.image_tool_results,
370            supports_web_search: s.supports_web_search,
371            image_generation: s.image_generation,
372            realtime: s.realtime,
373            call_timeout_secs: s.call_timeout_secs,
374        }
375    }
376}
377
378impl SessionLlmCapabilitySurface {
379    pub fn from_domain(id: &crate::meerkat_machine_types::SessionLlmCapabilitySurface) -> Self {
380        Self::from(id)
381    }
382}
383
384/// Typed capability-surface resolution status. Closed mirror of
385/// [`crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus`] —
386/// replaces the former JSON-stringified wrapper the DSL used to carry the
387/// two-state discriminant across the seam.
388///
389/// The DSL stores the variant directly on `ReconfigureSessionLlmIdentity`
390/// flow state; the shell maps to/from the domain enum via the `From` impls
391/// below — no `serde_json::to_string`, no string compares.
392#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
393pub enum SessionLlmCapabilitySurfaceStatus {
394    Resolved,
395    #[default]
396    Unresolved,
397}
398
399impl From<crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus>
400    for SessionLlmCapabilitySurfaceStatus
401{
402    fn from(status: crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus) -> Self {
403        match status {
404            crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus::Resolved => {
405                Self::Resolved
406            }
407            crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus::Unresolved => {
408                Self::Unresolved
409            }
410        }
411    }
412}
413
414impl From<SessionLlmCapabilitySurfaceStatus>
415    for crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus
416{
417    fn from(status: SessionLlmCapabilitySurfaceStatus) -> Self {
418        match status {
419            SessionLlmCapabilitySurfaceStatus::Resolved => Self::Resolved,
420            SessionLlmCapabilitySurfaceStatus::Unresolved => Self::Unresolved,
421        }
422    }
423}
424
425impl SessionLlmCapabilitySurfaceStatus {
426    pub fn from_domain(
427        id: &crate::meerkat_machine_types::SessionLlmCapabilitySurfaceStatus,
428    ) -> Self {
429        Self::from(*id)
430    }
431}
432
433/// Typed mirror of
434/// [`crate::meerkat_machine_types::SessionToolVisibilityDelta`] — structural
435/// projection using typed `ToolFilter` mirrors plus the two boolean change
436/// flags. Replaces the former `format!("{id:?}")` Debug-stringified wrapper.
437#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
438pub struct SessionToolVisibilityDelta {
439    pub previous_capability_base_filter: ToolFilter,
440    pub current_capability_base_filter: ToolFilter,
441    pub committed_visible_set_changed: bool,
442    pub revision_bumped: bool,
443}
444
445impl SessionToolVisibilityDelta {
446    pub fn from_domain(id: &crate::meerkat_machine_types::SessionToolVisibilityDelta) -> Self {
447        Self {
448            previous_capability_base_filter: ToolFilter::from(&id.previous_capability_base_filter),
449            current_capability_base_filter: ToolFilter::from(&id.current_capability_base_filter),
450            committed_visible_set_changed: id.committed_visible_set_changed,
451            revision_bumped: id.revision_bumped,
452        }
453    }
454}
455
456/// Typed mirror of [`meerkat_core::ToolFilter`] — closed 3-variant
457/// discriminant with a `BTreeSet<String>` name payload for
458/// `Allow`/`Deny` so the value is `Ord + Hash` and deterministic across
459/// iteration, matching the R3 `InputAbandonReason::MaxAttemptsExhausted {
460/// attempts }` pattern of carrying the discriminant's companion data in a
461/// field with stable ordering.
462#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
463pub enum ToolFilter {
464    #[default]
465    All,
466    Allow(std::collections::BTreeSet<String>),
467    Deny(std::collections::BTreeSet<String>),
468}
469
470impl From<&meerkat_core::ToolFilter> for ToolFilter {
471    fn from(f: &meerkat_core::ToolFilter) -> Self {
472        match f {
473            meerkat_core::ToolFilter::All => Self::All,
474            meerkat_core::ToolFilter::Allow(names) => {
475                Self::Allow(names.iter().map(|name| name.as_str().to_string()).collect())
476            }
477            meerkat_core::ToolFilter::Deny(names) => {
478                Self::Deny(names.iter().map(|name| name.as_str().to_string()).collect())
479            }
480        }
481    }
482}
483
484impl From<ToolFilter> for meerkat_core::ToolFilter {
485    fn from(f: ToolFilter) -> Self {
486        match f {
487            ToolFilter::All => Self::All,
488            ToolFilter::Allow(names) => Self::Allow(names.into_iter().collect()),
489            ToolFilter::Deny(names) => Self::Deny(names.into_iter().collect()),
490        }
491    }
492}
493
494impl ToolFilter {
495    pub fn from_domain(id: &meerkat_core::ToolFilter) -> Self {
496        Self::from(id)
497    }
498}
499
500/// Typed mirror of [`meerkat_core::types::ToolSourceKind`] — closed
501/// 10-variant discriminant for tool provenance classification.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
503pub enum ToolSourceKind {
504    #[default]
505    Builtin,
506    Shell,
507    Comms,
508    Memory,
509    Schedule,
510    Mob,
511    MobTasks,
512    Callback,
513    Mcp,
514    RustBundle,
515}
516
517impl From<&meerkat_core::types::ToolSourceKind> for ToolSourceKind {
518    fn from(k: &meerkat_core::types::ToolSourceKind) -> Self {
519        match k {
520            meerkat_core::types::ToolSourceKind::Builtin => Self::Builtin,
521            meerkat_core::types::ToolSourceKind::Shell => Self::Shell,
522            meerkat_core::types::ToolSourceKind::Comms => Self::Comms,
523            meerkat_core::types::ToolSourceKind::Memory => Self::Memory,
524            meerkat_core::types::ToolSourceKind::Schedule => Self::Schedule,
525            meerkat_core::types::ToolSourceKind::Mob => Self::Mob,
526            meerkat_core::types::ToolSourceKind::MobTasks => Self::MobTasks,
527            meerkat_core::types::ToolSourceKind::Callback => Self::Callback,
528            meerkat_core::types::ToolSourceKind::Mcp => Self::Mcp,
529            meerkat_core::types::ToolSourceKind::RustBundle => Self::RustBundle,
530        }
531    }
532}
533
534/// Typed mirror of [`meerkat_core::types::ToolProvenance`] — structural
535/// projection carried inside [`ToolVisibilityWitness`], using the typed
536/// `ToolSourceKind` discriminant mirror.
537#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
538pub struct ToolProvenance {
539    pub kind: ToolSourceKind,
540    pub source_id: String,
541}
542
543impl From<&meerkat_core::types::ToolProvenance> for ToolProvenance {
544    fn from(p: &meerkat_core::types::ToolProvenance) -> Self {
545        Self {
546            kind: ToolSourceKind::from(&p.kind),
547            source_id: p.source_id.to_string(),
548        }
549    }
550}
551
552/// Typed mirror of [`meerkat_core::ToolVisibilityWitness`] — structural
553/// projection of the two optional witness fields.
554#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
555pub struct ToolVisibilityWitness {
556    pub stable_owner_key: Option<String>,
557    pub last_seen_provenance: Option<ToolProvenance>,
558}
559
560impl From<&meerkat_core::ToolVisibilityWitness> for ToolVisibilityWitness {
561    fn from(w: &meerkat_core::ToolVisibilityWitness) -> Self {
562        Self {
563            stable_owner_key: w.stable_owner_key.clone(),
564            last_seen_provenance: w.last_seen_provenance.as_ref().map(ToolProvenance::from),
565        }
566    }
567}
568
569impl ToolVisibilityWitness {
570    pub fn from_domain(id: &meerkat_core::ToolVisibilityWitness) -> Self {
571        Self::from(id)
572    }
573
574    fn len(&self) -> u64 {
575        u64::from(self.last_seen_provenance.is_some())
576    }
577}
578
579/// Per-session realtime binding-state lifecycle.
580///
581/// Unit variants only — carried inside `MeerkatMachine` state as a closed
582/// set of phases. The default (`Unbound`) is paired with
583/// `realtime_binding_authority_epoch == None` by the
584/// `realtime_binding_epoch_consistency` invariant.
585///
586/// Default serde tagging reuses the variant names as string values
587/// (`"Unbound"`, `"BindingNotReady"`, `"BindingReady"`, `"ReplacementPending"`)
588/// to preserve wire-format compatibility with earlier stringly-typed clients.
589#[derive(
590    Debug,
591    Clone,
592    Copy,
593    PartialEq,
594    Eq,
595    PartialOrd,
596    Ord,
597    Hash,
598    Default,
599    serde::Serialize,
600    serde::Deserialize,
601)]
602pub enum RealtimeBindingState {
603    #[default]
604    Unbound,
605    BindingNotReady,
606    BindingReady,
607    ReplacementPending,
608}
609
610/// Per-session realtime reconnect retry lifecycle.
611///
612/// The realtime websocket shell supplies time observations and jittered retry
613/// deadlines, but the canonical cycle phase, attempt count, exhaustion, and
614/// public status projection live in the MeerkatMachine state.
615#[derive(
616    Debug,
617    Clone,
618    Copy,
619    PartialEq,
620    Eq,
621    PartialOrd,
622    Ord,
623    Hash,
624    Default,
625    serde::Serialize,
626    serde::Deserialize,
627)]
628pub enum RealtimeReconnectCycleState {
629    #[default]
630    Idle,
631    Reconnecting,
632    Exhausted,
633}
634
635/// Product-turn lifecycle phase for a provider-managed realtime session
636/// (U9 / dogma #4).
637///
638/// The realtime-WS shell previously tracked turn lifecycle as three shell
639/// locals (`product_turn_in_flight`, `product_turn_committed`,
640/// `product_output_started`). This enum collapses the product of those
641/// three orthogonal milestones into a closed set of phases the DSL owns:
642///
643/// - `Idle`: between turns — no input accepted yet.
644/// - `AwaitingProgress`: input accepted; no commit, no output observed.
645/// - `Committed`: `TurnCommitted` arrived but no output delta yet.
646/// - `OutputStarted`: output delta / tool call arrived but no commit yet.
647/// - `Preemptible`: both `TurnCommitted` and output have landed — the
648///   only state in which an input chunk should preempt the current
649///   provider-managed turn (the "committed turn has visible assistant-side
650///   progress" rule documented on `should_preempt_on_input`).
651///
652/// Transitions are idempotent via guard rejection: the runtime handle
653/// reports guard-rejected transitions as `Ok(false)` so the shell can
654/// fire unconditionally on every lifecycle event without tracking its
655/// own phase.
656#[derive(
657    Debug,
658    Clone,
659    Copy,
660    PartialEq,
661    Eq,
662    PartialOrd,
663    Ord,
664    Hash,
665    Default,
666    serde::Serialize,
667    serde::Deserialize,
668)]
669pub enum RealtimeProductTurnPhase {
670    #[default]
671    Idle,
672    AwaitingProgress,
673    Committed,
674    OutputStarted,
675    Preemptible,
676}
677
678/// Projection-freshness discriminant for the realtime provider session
679/// (dogma round 2, U-C / dogma #1, #3, #13, #20).
680///
681/// Replaces the shell-local `ProjectionFreshness` enum previously owned by
682/// `meerkat-rpc::realtime_ws`. Freshness truth is now canonical DSL state
683/// owned by the session's MeerkatMachine; the realtime-WS shell reads it via
684/// the [`RealtimeProductTurnHandle`] and fires typed inputs for each
685/// observer tick, turn terminal, and refresh-drain.
686///
687/// The `baseline_ms` companion field
688/// ([`MeerkatMachineState::realtime_projection_frontier_ms`]) pairs with this
689/// discriminant: it holds the `baseline_ms` while `Clean`, and the
690/// `new_at_ms` of the pending advance while `StaleDeferred` / `StaleImmediate`.
691#[derive(
692    Debug,
693    Clone,
694    Copy,
695    PartialEq,
696    Eq,
697    PartialOrd,
698    Ord,
699    Hash,
700    Default,
701    serde::Serialize,
702    serde::Deserialize,
703)]
704pub enum RealtimeProjectionFreshness {
705    /// Provider projection matches canonical session state as of
706    /// `realtime_projection_frontier_ms`. No refresh owed.
707    #[default]
708    Clean,
709    /// Canonical state advanced while the provider turn was live; refresh
710    /// blocked until the turn terminates so barge-in continuity isn't broken.
711    StaleDeferred,
712    /// Refresh owed at the next drain site (idle input-chunk arrival or
713    /// turn-end).
714    StaleImmediate,
715}
716
717/// Typed classification of a clean provider-session close for the realtime
718/// socket (dogma round 2, U-C / dogma #1, #3, #18, #20).
719///
720/// Replaces the shell-local boolean pair (`client_has_submitted_input`,
721/// `last_turn_terminally_completed`) previously owned by the realtime-WS
722/// dispatch loop. The DSL owns the classification; the shell reads
723/// [`RealtimeProductTurnHandle::reconnect_policy_on_clean_close`] at the
724/// clean-close branch point and dispatches on the typed value.
725///
726/// Semantics: a `CleanExit` means the session has no in-flight client work
727/// that would need to be recovered via reattach (either the client never
728/// submitted anything, or the last turn reached a terminal completion).
729/// `ReattachAndRecover` means the client issued work that has not yet
730/// reached a terminal completion, so a clean close is treated as a
731/// mid-work disconnect and the channel proactively re-opens.
732#[derive(
733    Debug,
734    Clone,
735    Copy,
736    PartialEq,
737    Eq,
738    PartialOrd,
739    Ord,
740    Hash,
741    Default,
742    serde::Serialize,
743    serde::Deserialize,
744)]
745pub enum RealtimeReconnectPolicy {
746    /// A clean close has nothing to recover — either the client never
747    /// submitted input on this session, or the last observed turn reached
748    /// a terminal completion.
749    #[default]
750    CleanExit,
751    /// The client issued work that has not yet reached a terminal turn
752    /// completion; a clean close is a mid-work disconnect and the channel
753    /// should proactively reattach.
754    ReattachAndRecover,
755}
756
757/// Bridging type for an MCP server identifier, matching the catalog type.
758/// Used as the key in `mcp_server_states` and carried on MCP lifecycle
759/// inputs and effects.
760#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
761pub struct McpServerId(pub String);
762
763impl<T: Into<String>> From<T> for McpServerId {
764    fn from(s: T) -> Self {
765        Self(s.into())
766    }
767}
768
769/// Bridging wrapper mapping [`meerkat_core::PeerCorrelationId`] into the DSL
770/// macro's type system. Keyed map values for `pending_peer_requests` and
771/// `inbound_peer_requests`; carried on every W1-A peer-lifecycle input and
772/// effect.
773#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
774pub struct PeerCorrelationId(pub String);
775
776impl From<meerkat_core::PeerCorrelationId> for PeerCorrelationId {
777    fn from(id: meerkat_core::PeerCorrelationId) -> Self {
778        Self(id.0.to_string())
779    }
780}
781
782impl From<uuid::Uuid> for PeerCorrelationId {
783    fn from(id: uuid::Uuid) -> Self {
784        Self(id.to_string())
785    }
786}
787
788impl From<String> for PeerCorrelationId {
789    fn from(s: String) -> Self {
790        Self(s)
791    }
792}
793
794impl From<&str> for PeerCorrelationId {
795    fn from(s: &str) -> Self {
796        Self(s.to_string())
797    }
798}
799
800/// Typed outbound peer-request state, mirroring
801/// [`meerkat_core::OutboundPeerRequestState`]. Unit variants only; failure
802/// reason travels on the `PeerResponseTerminalArrived` input's companion
803/// fields, not in the enum.
804#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
805pub enum OutboundPeerRequestState {
806    #[default]
807    Sent,
808    AcceptedProgress,
809    Completed,
810    Failed,
811    TimedOut,
812}
813
814impl From<meerkat_core::OutboundPeerRequestState> for OutboundPeerRequestState {
815    fn from(s: meerkat_core::OutboundPeerRequestState) -> Self {
816        match s {
817            meerkat_core::OutboundPeerRequestState::Sent => Self::Sent,
818            meerkat_core::OutboundPeerRequestState::AcceptedProgress => Self::AcceptedProgress,
819            meerkat_core::OutboundPeerRequestState::Completed => Self::Completed,
820            meerkat_core::OutboundPeerRequestState::Failed => Self::Failed,
821            meerkat_core::OutboundPeerRequestState::TimedOut => Self::TimedOut,
822            // core `#[non_exhaustive]` guard: new variants added there
823            // without a catalog mirror fall back to `Sent`. Detect at
824            // codegen time via the parity test, not at runtime.
825            _ => Self::Sent,
826        }
827    }
828}
829
830impl From<OutboundPeerRequestState> for meerkat_core::OutboundPeerRequestState {
831    fn from(s: OutboundPeerRequestState) -> Self {
832        match s {
833            OutboundPeerRequestState::Sent => Self::Sent,
834            OutboundPeerRequestState::AcceptedProgress => Self::AcceptedProgress,
835            OutboundPeerRequestState::Completed => Self::Completed,
836            OutboundPeerRequestState::Failed => Self::Failed,
837            OutboundPeerRequestState::TimedOut => Self::TimedOut,
838        }
839    }
840}
841
842/// Typed inbound peer-request state, mirroring
843/// [`meerkat_core::InboundPeerRequestState`].
844#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
845pub enum InboundPeerRequestState {
846    #[default]
847    Received,
848    Replied,
849}
850
851impl From<meerkat_core::InboundPeerRequestState> for InboundPeerRequestState {
852    fn from(s: meerkat_core::InboundPeerRequestState) -> Self {
853        match s {
854            meerkat_core::InboundPeerRequestState::Received => Self::Received,
855            meerkat_core::InboundPeerRequestState::Replied => Self::Replied,
856            _ => Self::Received,
857        }
858    }
859}
860
861impl From<InboundPeerRequestState> for meerkat_core::InboundPeerRequestState {
862    fn from(s: InboundPeerRequestState) -> Self {
863        match s {
864            InboundPeerRequestState::Received => Self::Received,
865            InboundPeerRequestState::Replied => Self::Replied,
866        }
867    }
868}
869
870/// Typed terminal disposition carried on `PeerResponseTerminalArrived`.
871/// Mirror of [`meerkat_core::handles::PeerTerminalDisposition`].
872#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
873pub enum PeerTerminalDisposition {
874    #[default]
875    Completed,
876    Failed,
877}
878
879impl From<meerkat_core::handles::PeerTerminalDisposition> for PeerTerminalDisposition {
880    fn from(d: meerkat_core::handles::PeerTerminalDisposition) -> Self {
881        match d {
882            meerkat_core::handles::PeerTerminalDisposition::Completed => Self::Completed,
883            meerkat_core::handles::PeerTerminalDisposition::Failed => Self::Failed,
884            _ => Self::Failed,
885        }
886    }
887}
888
889/// Typed lifecycle state of an interaction stream reservation (U6 / dogma #5).
890///
891/// Owns whether a reserved subscriber/stream channel is still claimable
892/// (`Reserved`), live with an attached consumer (`Attached`), or terminal
893/// (`Completed` after a terminal event won, `Expired` after the TTL elapsed
894/// without an attach, `ClosedEarly` after the consumer dropped the stream
895/// before terminal). Mirror of [`meerkat_core::InteractionStreamState`].
896#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
897pub enum InteractionStreamState {
898    #[default]
899    Reserved,
900    Attached,
901    Completed,
902    Expired,
903    ClosedEarly,
904}
905
906impl From<meerkat_core::InteractionStreamState> for InteractionStreamState {
907    fn from(s: meerkat_core::InteractionStreamState) -> Self {
908        match s {
909            meerkat_core::InteractionStreamState::Reserved => Self::Reserved,
910            meerkat_core::InteractionStreamState::Attached => Self::Attached,
911            meerkat_core::InteractionStreamState::Completed => Self::Completed,
912            meerkat_core::InteractionStreamState::Expired => Self::Expired,
913            meerkat_core::InteractionStreamState::ClosedEarly => Self::ClosedEarly,
914            _ => Self::Reserved,
915        }
916    }
917}
918
919impl From<InteractionStreamState> for meerkat_core::InteractionStreamState {
920    fn from(s: InteractionStreamState) -> Self {
921        match s {
922            InteractionStreamState::Reserved => Self::Reserved,
923            InteractionStreamState::Attached => Self::Attached,
924            InteractionStreamState::Completed => Self::Completed,
925            InteractionStreamState::Expired => Self::Expired,
926            InteractionStreamState::ClosedEarly => Self::ClosedEarly,
927        }
928    }
929}
930
931/// Per-server MCP connection lifecycle state. Matches the catalog copy;
932/// unit variants only so the DSL can reason about state via map inserts.
933/// Failure detail travels on the `McpServerFailed` input and
934/// `McpServerStateChanged` effect's companion fields, not on the enum.
935#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
936pub enum McpServerState {
937    #[default]
938    PendingConnect,
939    Connected,
940    Failed,
941    Disconnected,
942}
943
944/// Stable identity of a comms runtime instance (W2-G / issue #264).
945///
946/// The runtime derives this string from the `Arc<dyn CommsRuntime>` pointer
947/// address via `CommsRuntimeId::from_runtime()`. The DSL treats it as an
948/// opaque newtype; two distinct `Arc`s produce distinct ids so the owner
949/// invariant can catch silent transport swaps.
950#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
951pub struct CommsRuntimeId(pub String);
952
953impl<T: Into<String>> From<T> for CommsRuntimeId {
954    fn from(s: T) -> Self {
955        Self(s.into())
956    }
957}
958
959impl CommsRuntimeId {
960    /// Derive a stable id from an `Arc<dyn CommsRuntime>`'s pointer address.
961    ///
962    /// Two `Arc` instances with the same pointee produce the same id; two
963    /// distinct `Arc` instances produce distinct ids even if their contents
964    /// are equivalent. This is sufficient for detecting silent transport
965    /// swaps at the DSL boundary.
966    pub fn from_runtime(runtime: &std::sync::Arc<dyn meerkat_core::agent::CommsRuntime>) -> Self {
967        let ptr = std::sync::Arc::as_ptr(runtime).cast::<()>() as usize;
968        Self(format!("comms-runtime-0x{ptr:x}"))
969    }
970}
971
972/// Mob instance identifier for peer-ingress ownership (W2-G / issue #264).
973///
974/// Bridging newtype mirroring `meerkat_mob::ids::MobId`. The DSL layer keeps
975/// this opaque because `meerkat-runtime` does not depend on `meerkat-mob`;
976/// the shell stringifies the real `MobId` before firing `AttachMobIngress`.
977#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
978pub struct MobId(pub String);
979
980impl<T: Into<String>> From<T> for MobId {
981    fn from(s: T) -> Self {
982        Self(s.into())
983    }
984}
985
986/// Parsed transport envelope class for peer ingress.
987///
988/// This is the mechanical shape comms may derive from a wire envelope before
989/// semantic admission. The DSL consumes it to own the peer-input class,
990/// auth-exemption, lifecycle, silent-routing, and response-terminal facts.
991#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
992pub enum PeerIngressEnvelopeClass {
993    #[default]
994    Message,
995    Request,
996    Lifecycle,
997    Response,
998    Ack,
999}
1000
1001/// DSL-owned admitted ingress kind.
1002#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1003pub enum PeerIngressAdmittedKind {
1004    #[default]
1005    Message,
1006    Request,
1007    Response,
1008    Ack,
1009    PlainEvent,
1010}
1011
1012/// DSL-owned peer input class.
1013#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1014pub enum PeerIngressInputClass {
1015    #[default]
1016    ActionableMessage,
1017    ActionableRequest,
1018    ResponseProgress,
1019    ResponseTerminal,
1020    PeerLifecycleAdded,
1021    PeerLifecycleRetired,
1022    PeerLifecycleUnwired,
1023    SilentRequest,
1024    Ack,
1025    PlainEvent,
1026}
1027
1028/// DSL-owned peer lifecycle classifier.
1029#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1030pub enum PeerIngressLifecycleClass {
1031    #[default]
1032    PeerAdded,
1033    PeerRetired,
1034    PeerUnwired,
1035}
1036
1037/// DSL-owned peer ingress auth classifier.
1038#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1039pub enum PeerIngressAuthClass {
1040    #[default]
1041    Required,
1042    SupervisorBridgeExempt,
1043}
1044
1045/// Parsed response status for peer ingress.
1046#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1047pub enum PeerIngressResponseStatus {
1048    #[default]
1049    Accepted,
1050    Completed,
1051    Failed,
1052}
1053
1054/// DSL-owned response progress/terminal classifier.
1055#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1056pub enum PeerIngressResponseTerminality {
1057    #[default]
1058    Progress,
1059    TerminalCompleted,
1060    TerminalFailed,
1061}
1062
1063/// Peer-ingress transport capability ownership kind (W2-G / issue #264).
1064///
1065/// Paired with `peer_ingress_comms_runtime_id` and `peer_ingress_mob_id` in
1066/// DSL state; `peer_ingress_owner_consistency` enforces pairing. Silent
1067/// downgrade `MobOwned` → `SessionOwned` is structurally impossible:
1068/// `AttachSessionIngress` requires `Unattached`; `AttachMobIngress` permits
1069/// `Unattached` or `SessionOwned` but never `MobOwned` → `SessionOwned`.
1070#[derive(
1071    Debug,
1072    Clone,
1073    Copy,
1074    PartialEq,
1075    Eq,
1076    PartialOrd,
1077    Ord,
1078    Hash,
1079    Default,
1080    serde::Serialize,
1081    serde::Deserialize,
1082)]
1083pub enum PeerIngressOwnerKind {
1084    #[default]
1085    Unattached,
1086    SessionOwned,
1087    MobOwned,
1088}
1089
1090/// Supervisor-bridge authorization kind (Wave 3 D Row 21).
1091///
1092/// Paired with `supervisor_bound_{name, peer_id, address, epoch}` in DSL
1093/// state; `supervisor_binding_consistency` enforces pairing. Rotation is
1094/// structural: `BindSupervisor` requires `Unbound`; `AuthorizeSupervisor`
1095/// requires `Bound`; `RevokeSupervisor` requires `Bound` and returns to
1096/// `Unbound`. Before Wave 3 D this fact lived as an `Option<AuthorizedSupervisorState>`
1097/// on the comms drain task's stack — the identity and epoch of the
1098/// authorized supervisor were helper-local while the corresponding trust
1099/// edge was router-owned. Moving the authorization discriminant + epoch
1100/// into DSL state collapses that split ownership.
1101#[derive(
1102    Debug,
1103    Clone,
1104    Copy,
1105    PartialEq,
1106    Eq,
1107    PartialOrd,
1108    Ord,
1109    Hash,
1110    Default,
1111    serde::Serialize,
1112    serde::Deserialize,
1113)]
1114pub enum SupervisorBindingKind {
1115    #[default]
1116    Unbound,
1117    Bound,
1118}
1119
1120/// Typed turn-execution phase, mirrored 1:1 by the closed set of literals the
1121/// DSL transitions assign to `turn_phase`. Replaces the prior stringly-typed
1122/// encoding so the ephemeral driver and runtime handles consume an exhaustive
1123/// enum instead of parsing folklore.
1124#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1125pub enum TurnPhase {
1126    #[default]
1127    Ready,
1128    ApplyingPrimitive,
1129    CallingLlm,
1130    WaitingForOps,
1131    DrainingBoundary,
1132    Extracting,
1133    ErrorRecovery,
1134    Cancelling,
1135    Completed,
1136    Failed,
1137    Cancelled,
1138}
1139
1140impl TurnPhase {
1141    pub const fn as_str(self) -> &'static str {
1142        match self {
1143            Self::Ready => "Ready",
1144            Self::ApplyingPrimitive => "ApplyingPrimitive",
1145            Self::CallingLlm => "CallingLlm",
1146            Self::WaitingForOps => "WaitingForOps",
1147            Self::DrainingBoundary => "DrainingBoundary",
1148            Self::Extracting => "Extracting",
1149            Self::ErrorRecovery => "ErrorRecovery",
1150            Self::Cancelling => "Cancelling",
1151            Self::Completed => "Completed",
1152            Self::Failed => "Failed",
1153            Self::Cancelled => "Cancelled",
1154        }
1155    }
1156}
1157
1158/// Typed registration substate. Closed set of literals previously assigned to
1159/// `registration_phase`.
1160#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1161pub enum RegistrationPhase {
1162    #[default]
1163    Queuing,
1164    Active,
1165}
1166
1167impl RegistrationPhase {
1168    pub const fn as_str(self) -> &'static str {
1169        match self {
1170            Self::Queuing => "Queuing",
1171            Self::Active => "Active",
1172        }
1173    }
1174}
1175
1176/// Typed comms drain substate. Mirrors the closed set of literals the DSL
1177/// transitions assign to `drain_phase`.
1178#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1179pub enum DrainPhase {
1180    #[default]
1181    Inactive,
1182    Running,
1183    Stopped,
1184    ExitedRespawnable,
1185}
1186
1187impl DrainPhase {
1188    pub const fn as_str(self) -> &'static str {
1189        match self {
1190            Self::Inactive => "Inactive",
1191            Self::Running => "Running",
1192            Self::Stopped => "Stopped",
1193            Self::ExitedRespawnable => "ExitedRespawnable",
1194        }
1195    }
1196}
1197
1198/// Typed comms drain mode. Mirrors `crate::meerkat_machine::CommsDrainMode`
1199/// (which is the shell-side enum) so the DSL can hold a closed set of typed
1200/// variants instead of a `Debug`-formatted string.
1201#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1202pub enum DrainMode {
1203    #[default]
1204    Timed,
1205    AttachedSession,
1206    PersistentHost,
1207}
1208
1209impl DrainMode {
1210    pub const fn as_str(self) -> &'static str {
1211        match self {
1212            Self::Timed => "Timed",
1213            Self::AttachedSession => "AttachedSession",
1214            Self::PersistentHost => "PersistentHost",
1215        }
1216    }
1217}
1218
1219impl From<crate::meerkat_machine::CommsDrainMode> for DrainMode {
1220    fn from(mode: crate::meerkat_machine::CommsDrainMode) -> Self {
1221        match mode {
1222            crate::meerkat_machine::CommsDrainMode::Timed => Self::Timed,
1223            crate::meerkat_machine::CommsDrainMode::AttachedSession => Self::AttachedSession,
1224            crate::meerkat_machine::CommsDrainMode::PersistentHost => Self::PersistentHost,
1225        }
1226    }
1227}
1228
1229/// Typed external-tool surface global phase. Closed set of literals previously
1230/// assigned to `surface_phase`.
1231#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1232pub enum SurfacePhase {
1233    #[default]
1234    Operating,
1235    Shutdown,
1236}
1237
1238impl SurfacePhase {
1239    pub const fn as_str(self) -> &'static str {
1240        match self {
1241            Self::Operating => "Operating",
1242            Self::Shutdown => "Shutdown",
1243        }
1244    }
1245}
1246
1247/// Typed live-topology reconfigure phase. Closed set of literals previously
1248/// assigned to `live_topology_phase`. The catalog DSL holds a parallel copy.
1249#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1250pub enum LiveTopologyPhase {
1251    #[default]
1252    Idle,
1253    Reconfiguring,
1254    Detached,
1255    HostIdentityApplied,
1256    HostVisibilityApplied,
1257}
1258
1259impl LiveTopologyPhase {
1260    pub const fn as_str(self) -> &'static str {
1261        match self {
1262            Self::Idle => "Idle",
1263            Self::Reconfiguring => "Reconfiguring",
1264            Self::Detached => "Detached",
1265            Self::HostIdentityApplied => "HostIdentityApplied",
1266            Self::HostVisibilityApplied => "HostVisibilityApplied",
1267        }
1268    }
1269}
1270
1271/// Typed input-lifecycle phase, mirroring the closed set of literals the DSL
1272/// transitions assign to `input_phases`. The shell projects from this onto the
1273/// richer `crate::input_state::InputLifecycleState` (which keeps an `Accepted`
1274/// pre-DSL-admission variant the DSL itself never writes).
1275#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1276pub enum InputPhase {
1277    #[default]
1278    Queued,
1279    Staged,
1280    Applied,
1281    AppliedPendingConsumption,
1282    Consumed,
1283    Superseded,
1284    Coalesced,
1285    Abandoned,
1286}
1287
1288impl InputPhase {
1289    pub const fn as_str(self) -> &'static str {
1290        match self {
1291            Self::Queued => "Queued",
1292            Self::Staged => "Staged",
1293            Self::Applied => "Applied",
1294            Self::AppliedPendingConsumption => "AppliedPendingConsumption",
1295            Self::Consumed => "Consumed",
1296            Self::Superseded => "Superseded",
1297            Self::Coalesced => "Coalesced",
1298            Self::Abandoned => "Abandoned",
1299        }
1300    }
1301}
1302
1303/// Typed input terminal kind, mirroring the closed set of literals the DSL
1304/// transitions assign to `input_terminal_kind`. The companion fields
1305/// (`input_superseded_by`, `input_aggregate_id`, `input_abandon_reason`,
1306/// `input_abandon_attempt_count`) carry payload metadata for variants that
1307/// need it.
1308#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1309pub enum InputTerminalKind {
1310    #[default]
1311    Consumed,
1312    Superseded,
1313    Coalesced,
1314    Abandoned,
1315}
1316
1317impl InputTerminalKind {
1318    pub const fn as_str(self) -> &'static str {
1319        match self {
1320            Self::Consumed => "Consumed",
1321            Self::Superseded => "Superseded",
1322            Self::Coalesced => "Coalesced",
1323            Self::Abandoned => "Abandoned",
1324        }
1325    }
1326}
1327
1328/// Typed pending external-surface op. Closed set of literals previously
1329/// assigned to `surface_pending_op`.
1330#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1331pub enum SurfacePendingOp {
1332    #[default]
1333    None,
1334    Add,
1335    Reload,
1336}
1337
1338impl SurfacePendingOp {
1339    pub const fn as_str(self) -> &'static str {
1340        match self {
1341            Self::None => "None",
1342            Self::Add => "Add",
1343            Self::Reload => "Reload",
1344        }
1345    }
1346}
1347
1348/// Typed staged external-surface op. Closed set of literals previously
1349/// assigned to `surface_staged_op`.
1350#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1351pub enum SurfaceStagedOp {
1352    #[default]
1353    None,
1354    Add,
1355    Remove,
1356    Reload,
1357}
1358
1359impl SurfaceStagedOp {
1360    pub const fn as_str(self) -> &'static str {
1361        match self {
1362            Self::None => "None",
1363            Self::Add => "Add",
1364            Self::Remove => "Remove",
1365            Self::Reload => "Reload",
1366        }
1367    }
1368}
1369
1370/// Typed turn primitive kind. Closed mirror of
1371/// [`meerkat_core::turn_execution_authority::TurnPrimitiveKind`] — replaces the
1372/// former literal-string `primitive_kind` field and `StartConversationRun`
1373/// input field.
1374#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1375pub enum TurnPrimitiveKind {
1376    #[default]
1377    None,
1378    ConversationTurn,
1379    ImmediateAppend,
1380    ImmediateContextAppend,
1381}
1382
1383impl From<meerkat_core::turn_execution_authority::TurnPrimitiveKind> for TurnPrimitiveKind {
1384    fn from(kind: meerkat_core::turn_execution_authority::TurnPrimitiveKind) -> Self {
1385        match kind {
1386            meerkat_core::turn_execution_authority::TurnPrimitiveKind::None => Self::None,
1387            meerkat_core::turn_execution_authority::TurnPrimitiveKind::ConversationTurn => {
1388                Self::ConversationTurn
1389            }
1390            meerkat_core::turn_execution_authority::TurnPrimitiveKind::ImmediateAppend => {
1391                Self::ImmediateAppend
1392            }
1393            meerkat_core::turn_execution_authority::TurnPrimitiveKind::ImmediateContextAppend => {
1394                Self::ImmediateContextAppend
1395            }
1396        }
1397    }
1398}
1399
1400impl From<TurnPrimitiveKind> for meerkat_core::turn_execution_authority::TurnPrimitiveKind {
1401    fn from(kind: TurnPrimitiveKind) -> Self {
1402        match kind {
1403            TurnPrimitiveKind::None => Self::None,
1404            TurnPrimitiveKind::ConversationTurn => Self::ConversationTurn,
1405            TurnPrimitiveKind::ImmediateAppend => Self::ImmediateAppend,
1406            TurnPrimitiveKind::ImmediateContextAppend => Self::ImmediateContextAppend,
1407        }
1408    }
1409}
1410
1411/// Typed turn primitive content shape. Closed mirror of
1412/// [`meerkat_core::turn_execution_authority::ContentShape`] so the runtime DSL
1413/// carries the same contract instead of local string labels.
1414#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1415pub enum ContentShape {
1416    #[default]
1417    Conversation,
1418    ConversationAndContext,
1419    Context,
1420    Empty,
1421    ImmediateAppend,
1422    ImmediateContext,
1423}
1424
1425impl ContentShape {
1426    pub const fn as_str(self) -> &'static str {
1427        match self {
1428            Self::Conversation => {
1429                meerkat_core::turn_execution_authority::ContentShape::Conversation.as_str()
1430            }
1431            Self::ConversationAndContext => {
1432                meerkat_core::turn_execution_authority::ContentShape::ConversationAndContext
1433                    .as_str()
1434            }
1435            Self::Context => meerkat_core::turn_execution_authority::ContentShape::Context.as_str(),
1436            Self::Empty => meerkat_core::turn_execution_authority::ContentShape::Empty.as_str(),
1437            Self::ImmediateAppend => {
1438                meerkat_core::turn_execution_authority::ContentShape::ImmediateAppend.as_str()
1439            }
1440            Self::ImmediateContext => {
1441                meerkat_core::turn_execution_authority::ContentShape::ImmediateContext.as_str()
1442            }
1443        }
1444    }
1445}
1446
1447impl std::fmt::Display for ContentShape {
1448    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1449        f.write_str(self.as_str())
1450    }
1451}
1452
1453impl From<meerkat_core::turn_execution_authority::ContentShape> for ContentShape {
1454    fn from(shape: meerkat_core::turn_execution_authority::ContentShape) -> Self {
1455        match shape {
1456            meerkat_core::turn_execution_authority::ContentShape::Conversation => {
1457                Self::Conversation
1458            }
1459            meerkat_core::turn_execution_authority::ContentShape::ConversationAndContext => {
1460                Self::ConversationAndContext
1461            }
1462            meerkat_core::turn_execution_authority::ContentShape::Context => Self::Context,
1463            meerkat_core::turn_execution_authority::ContentShape::Empty => Self::Empty,
1464            meerkat_core::turn_execution_authority::ContentShape::ImmediateAppend => {
1465                Self::ImmediateAppend
1466            }
1467            meerkat_core::turn_execution_authority::ContentShape::ImmediateContext => {
1468                Self::ImmediateContext
1469            }
1470        }
1471    }
1472}
1473
1474impl From<ContentShape> for meerkat_core::turn_execution_authority::ContentShape {
1475    fn from(shape: ContentShape) -> Self {
1476        match shape {
1477            ContentShape::Conversation => Self::Conversation,
1478            ContentShape::ConversationAndContext => Self::ConversationAndContext,
1479            ContentShape::Context => Self::Context,
1480            ContentShape::Empty => Self::Empty,
1481            ContentShape::ImmediateAppend => Self::ImmediateAppend,
1482            ContentShape::ImmediateContext => Self::ImmediateContext,
1483        }
1484    }
1485}
1486
1487/// Typed turn terminal outcome. Closed mirror of
1488/// [`meerkat_core::turn_execution_authority::TurnTerminalOutcome`] — replaces
1489/// the former literal-string `terminal_outcome` field.
1490#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1491pub enum TurnTerminalOutcome {
1492    #[default]
1493    None,
1494    Completed,
1495    Failed,
1496    Cancelled,
1497    BudgetExhausted,
1498    TimeBudgetExceeded,
1499    StructuredOutputValidationFailed,
1500}
1501
1502impl From<meerkat_core::turn_execution_authority::TurnTerminalOutcome> for TurnTerminalOutcome {
1503    fn from(outcome: meerkat_core::turn_execution_authority::TurnTerminalOutcome) -> Self {
1504        match outcome {
1505            meerkat_core::turn_execution_authority::TurnTerminalOutcome::None => Self::None,
1506            meerkat_core::turn_execution_authority::TurnTerminalOutcome::Completed => {
1507                Self::Completed
1508            }
1509            meerkat_core::turn_execution_authority::TurnTerminalOutcome::Failed => Self::Failed,
1510            meerkat_core::turn_execution_authority::TurnTerminalOutcome::Cancelled => {
1511                Self::Cancelled
1512            }
1513            meerkat_core::turn_execution_authority::TurnTerminalOutcome::BudgetExhausted => {
1514                Self::BudgetExhausted
1515            }
1516            meerkat_core::turn_execution_authority::TurnTerminalOutcome::TimeBudgetExceeded => {
1517                Self::TimeBudgetExceeded
1518            }
1519            meerkat_core::turn_execution_authority::TurnTerminalOutcome::StructuredOutputValidationFailed => {
1520                Self::StructuredOutputValidationFailed
1521            }
1522        }
1523    }
1524}
1525
1526impl From<TurnTerminalOutcome> for meerkat_core::turn_execution_authority::TurnTerminalOutcome {
1527    fn from(outcome: TurnTerminalOutcome) -> Self {
1528        match outcome {
1529            TurnTerminalOutcome::None => Self::None,
1530            TurnTerminalOutcome::Completed => Self::Completed,
1531            TurnTerminalOutcome::Failed => Self::Failed,
1532            TurnTerminalOutcome::Cancelled => Self::Cancelled,
1533            TurnTerminalOutcome::BudgetExhausted => Self::BudgetExhausted,
1534            TurnTerminalOutcome::TimeBudgetExceeded => Self::TimeBudgetExceeded,
1535            TurnTerminalOutcome::StructuredOutputValidationFailed => {
1536                Self::StructuredOutputValidationFailed
1537            }
1538        }
1539    }
1540}
1541
1542/// Typed turn terminal cause. Closed mirror of
1543/// [`meerkat_core::turn_execution_authority::TurnTerminalCauseKind`] carried by
1544/// MeerkatMachine terminal failure inputs/effects so display messages cannot
1545/// classify terminal failures.
1546#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1547pub enum TurnTerminalCauseKind {
1548    #[default]
1549    Unknown,
1550    HookDenied,
1551    HookFailure,
1552    LlmFailure,
1553    ToolFailure,
1554    StructuredOutputValidationFailed,
1555    BudgetExhausted,
1556    TimeBudgetExceeded,
1557    RetryExhausted,
1558    TurnLimitReached,
1559    RuntimeApplyFailure,
1560    FatalFailure,
1561}
1562
1563impl From<meerkat_core::turn_execution_authority::TurnTerminalCauseKind> for TurnTerminalCauseKind {
1564    fn from(kind: meerkat_core::turn_execution_authority::TurnTerminalCauseKind) -> Self {
1565        match kind {
1566            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::Unknown => {
1567                Self::Unknown
1568            }
1569            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::HookDenied => {
1570                Self::HookDenied
1571            }
1572            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::HookFailure => {
1573                Self::HookFailure
1574            }
1575            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::LlmFailure => {
1576                Self::LlmFailure
1577            }
1578            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::ToolFailure => {
1579                Self::ToolFailure
1580            }
1581            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::StructuredOutputValidationFailed => {
1582                Self::StructuredOutputValidationFailed
1583            }
1584            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::BudgetExhausted => {
1585                Self::BudgetExhausted
1586            }
1587            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::TimeBudgetExceeded => {
1588                Self::TimeBudgetExceeded
1589            }
1590            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::RetryExhausted => {
1591                Self::RetryExhausted
1592            }
1593            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::TurnLimitReached => {
1594                Self::TurnLimitReached
1595            }
1596            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::RuntimeApplyFailure => {
1597                Self::RuntimeApplyFailure
1598            }
1599            meerkat_core::turn_execution_authority::TurnTerminalCauseKind::FatalFailure => {
1600                Self::FatalFailure
1601            }
1602        }
1603    }
1604}
1605
1606impl From<TurnTerminalCauseKind> for meerkat_core::turn_execution_authority::TurnTerminalCauseKind {
1607    fn from(kind: TurnTerminalCauseKind) -> Self {
1608        match kind {
1609            TurnTerminalCauseKind::Unknown => Self::Unknown,
1610            TurnTerminalCauseKind::HookDenied => Self::HookDenied,
1611            TurnTerminalCauseKind::HookFailure => Self::HookFailure,
1612            TurnTerminalCauseKind::LlmFailure => Self::LlmFailure,
1613            TurnTerminalCauseKind::ToolFailure => Self::ToolFailure,
1614            TurnTerminalCauseKind::StructuredOutputValidationFailed => {
1615                Self::StructuredOutputValidationFailed
1616            }
1617            TurnTerminalCauseKind::BudgetExhausted => Self::BudgetExhausted,
1618            TurnTerminalCauseKind::TimeBudgetExceeded => Self::TimeBudgetExceeded,
1619            TurnTerminalCauseKind::RetryExhausted => Self::RetryExhausted,
1620            TurnTerminalCauseKind::TurnLimitReached => Self::TurnLimitReached,
1621            TurnTerminalCauseKind::RuntimeApplyFailure => Self::RuntimeApplyFailure,
1622            TurnTerminalCauseKind::FatalFailure => Self::FatalFailure,
1623        }
1624    }
1625}
1626
1627/// Typed classifier for failures surfaced by the runtime apply loop when a
1628/// `CoreExecutor::apply` call fails and terminalizes the runtime turn.
1629/// The companion `last_runtime_apply_failure_message` state field carries the
1630/// human-readable projection.
1631#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1632pub enum RuntimeApplyFailureCause {
1633    #[default]
1634    Unknown,
1635    PrimitiveRejected,
1636    RuntimeContextApply,
1637    RuntimeTurn,
1638    HookDenied,
1639    HookRuntimeFailure,
1640    ExecutorStopped,
1641    ExecutorControlFailed,
1642    ExecutorInternal,
1643}
1644
1645impl From<meerkat_core::lifecycle::CoreApplyFailureCauseKind> for RuntimeApplyFailureCause {
1646    fn from(kind: meerkat_core::lifecycle::CoreApplyFailureCauseKind) -> Self {
1647        match kind {
1648            meerkat_core::lifecycle::CoreApplyFailureCauseKind::PrimitiveRejected => {
1649                Self::PrimitiveRejected
1650            }
1651            meerkat_core::lifecycle::CoreApplyFailureCauseKind::RuntimeContextApply => {
1652                Self::RuntimeContextApply
1653            }
1654            meerkat_core::lifecycle::CoreApplyFailureCauseKind::RuntimeTurn => Self::RuntimeTurn,
1655            meerkat_core::lifecycle::CoreApplyFailureCauseKind::HookDenied => Self::HookDenied,
1656            meerkat_core::lifecycle::CoreApplyFailureCauseKind::HookRuntimeFailure => {
1657                Self::HookRuntimeFailure
1658            }
1659            meerkat_core::lifecycle::CoreApplyFailureCauseKind::ExecutorStopped => {
1660                Self::ExecutorStopped
1661            }
1662            meerkat_core::lifecycle::CoreApplyFailureCauseKind::ExecutorControlFailed => {
1663                Self::ExecutorControlFailed
1664            }
1665            meerkat_core::lifecycle::CoreApplyFailureCauseKind::ExecutorInternal => {
1666                Self::ExecutorInternal
1667            }
1668            meerkat_core::lifecycle::CoreApplyFailureCauseKind::Unknown => Self::Unknown,
1669            _ => Self::Unknown,
1670        }
1671    }
1672}
1673
1674impl From<&meerkat_core::lifecycle::CoreApplyFailureCause> for RuntimeApplyFailureCause {
1675    fn from(cause: &meerkat_core::lifecycle::CoreApplyFailureCause) -> Self {
1676        Self::from(cause.kind)
1677    }
1678}
1679
1680/// Typed pre-run phase marker. Closed set: `idle`, `attached`, `retired`.
1681/// Replaces the former literal-string `pre_run_phase` field.
1682#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1683pub enum PreRunPhase {
1684    #[default]
1685    Idle,
1686    Attached,
1687    Retired,
1688}
1689
1690/// Typed runtime notice classifier for the `RuntimeNotice` effect. Closed set
1691/// of per-transition runtime lifecycle markers (drain exited, runtime reset,
1692/// executor stopped/exited, runtime recovered) emitted by the runtime-control
1693/// plane. Replaces the former literal-string `kind` field on `RuntimeNotice`
1694/// so the shell dispatcher matches exhaustively on a typed discriminant
1695/// instead of comparing string literals. `detail` stays `String` — it's a
1696/// free-form diagnostic message that accompanies the kind.
1697#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1698pub enum RuntimeNoticeKind {
1699    #[default]
1700    Drain,
1701    Reset,
1702    Stop,
1703    Exit,
1704    Recover,
1705}
1706
1707/// Closed classifier for runtime-loop executor effects emitted as neutral DSL
1708/// facts before the runtime shell converts them to sealed executable effects.
1709#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1710pub enum RuntimeEffectKind {
1711    #[default]
1712    CancelAfterBoundary,
1713    StopRuntimeExecutor,
1714}
1715
1716/// Typed reason classifier for the `TurnRunCancelled` effect. Closed set of
1717/// cancellation-observation origins emitted when a turn's cancellation
1718/// request lands at an observable boundary. Replaces the former literal-
1719/// string `reason` field on `TurnRunCancelled`. Only one origin is emitted
1720/// today (`Observed`, fired by the `CancellationObserved` transition), but
1721/// this remains a closed classifier not a free-form message — future
1722/// cancellation origins extend the enum rather than reintroducing strings.
1723#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1724pub enum TurnCancellationReason {
1725    #[default]
1726    Observed,
1727}
1728
1729/// Typed recoverable LLM retry failure classifier. Closed mirror of
1730/// [`meerkat_core::retry::LlmRetryFailureKind`] so retry authority records the
1731/// retry cause as data, not as a parsed diagnostic string.
1732#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1733pub enum LlmRetryFailureKind {
1734    #[default]
1735    RateLimited,
1736    NetworkTimeout,
1737    CallTimeout,
1738    RetryableProviderError,
1739}
1740
1741impl From<meerkat_core::retry::LlmRetryFailureKind> for LlmRetryFailureKind {
1742    fn from(kind: meerkat_core::retry::LlmRetryFailureKind) -> Self {
1743        match kind {
1744            meerkat_core::retry::LlmRetryFailureKind::RateLimited => Self::RateLimited,
1745            meerkat_core::retry::LlmRetryFailureKind::NetworkTimeout => Self::NetworkTimeout,
1746            meerkat_core::retry::LlmRetryFailureKind::CallTimeout => Self::CallTimeout,
1747            meerkat_core::retry::LlmRetryFailureKind::RetryableProviderError => {
1748                Self::RetryableProviderError
1749            }
1750        }
1751    }
1752}
1753
1754/// Typed admission-signal classifier for the `PostAdmissionSignal` effect.
1755/// Closed set of post-admission wake/interrupt intents emitted by the
1756/// ingress authority so the shell dispatcher matches exhaustively on a
1757/// typed discriminant instead of comparing string literals. Mirrors the
1758/// shell-side `driver::ephemeral::PostAdmissionSignal` strength ordering
1759/// (WakeLoop < InterruptYielding < RequestImmediateProcessing); the
1760/// shell enum additionally carries a `None` bottom that the DSL never
1761/// emits, so only the three emitted variants appear here.
1762#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1763pub enum PostAdmissionSignalKind {
1764    #[default]
1765    WakeLoop,
1766    InterruptYielding,
1767    RequestImmediateProcessing,
1768}
1769
1770/// Typed base lifecycle state for an external tool surface. Closed mirror of
1771/// [`meerkat_core::tool_scope::ExternalToolSurfaceBaseState`] — replaces the
1772/// former literal-string values in `surface_base_state`.
1773#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1774pub enum ExternalToolSurfaceBaseState {
1775    #[default]
1776    Absent,
1777    Active,
1778    Removing,
1779    Removed,
1780}
1781
1782impl From<meerkat_core::tool_scope::ExternalToolSurfaceBaseState> for ExternalToolSurfaceBaseState {
1783    fn from(state: meerkat_core::tool_scope::ExternalToolSurfaceBaseState) -> Self {
1784        match state {
1785            meerkat_core::tool_scope::ExternalToolSurfaceBaseState::Absent => Self::Absent,
1786            meerkat_core::tool_scope::ExternalToolSurfaceBaseState::Active => Self::Active,
1787            meerkat_core::tool_scope::ExternalToolSurfaceBaseState::Removing => Self::Removing,
1788            meerkat_core::tool_scope::ExternalToolSurfaceBaseState::Removed => Self::Removed,
1789        }
1790    }
1791}
1792
1793impl From<ExternalToolSurfaceBaseState> for meerkat_core::tool_scope::ExternalToolSurfaceBaseState {
1794    fn from(state: ExternalToolSurfaceBaseState) -> Self {
1795        match state {
1796            ExternalToolSurfaceBaseState::Absent => Self::Absent,
1797            ExternalToolSurfaceBaseState::Active => Self::Active,
1798            ExternalToolSurfaceBaseState::Removing => Self::Removing,
1799            ExternalToolSurfaceBaseState::Removed => Self::Removed,
1800        }
1801    }
1802}
1803
1804/// Typed last-delta operation for an external tool surface. Closed mirror of
1805/// [`meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation`] — replaces
1806/// the former literal-string values in `surface_last_delta_operation`.
1807#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1808pub enum ExternalToolSurfaceDeltaOperation {
1809    #[default]
1810    None,
1811    Add,
1812    Remove,
1813    Reload,
1814}
1815
1816impl From<meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation>
1817    for ExternalToolSurfaceDeltaOperation
1818{
1819    fn from(op: meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation) -> Self {
1820        match op {
1821            meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation::None => Self::None,
1822            meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation::Add => Self::Add,
1823            meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation::Remove => Self::Remove,
1824            meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation::Reload => Self::Reload,
1825        }
1826    }
1827}
1828
1829impl From<ExternalToolSurfaceDeltaOperation>
1830    for meerkat_core::tool_scope::ExternalToolSurfaceDeltaOperation
1831{
1832    fn from(op: ExternalToolSurfaceDeltaOperation) -> Self {
1833        match op {
1834            ExternalToolSurfaceDeltaOperation::None => Self::None,
1835            ExternalToolSurfaceDeltaOperation::Add => Self::Add,
1836            ExternalToolSurfaceDeltaOperation::Remove => Self::Remove,
1837            ExternalToolSurfaceDeltaOperation::Reload => Self::Reload,
1838        }
1839    }
1840}
1841
1842/// Typed last-delta phase for an external tool surface. Closed mirror of
1843/// [`meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase`] — replaces the
1844/// former literal-string values in `surface_last_delta_phase`.
1845#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1846pub enum ExternalToolSurfaceDeltaPhase {
1847    #[default]
1848    None,
1849    Pending,
1850    Applied,
1851    Draining,
1852    Failed,
1853    Forced,
1854}
1855
1856impl From<meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase>
1857    for ExternalToolSurfaceDeltaPhase
1858{
1859    fn from(phase: meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase) -> Self {
1860        match phase {
1861            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::None => Self::None,
1862            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::Pending => Self::Pending,
1863            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::Applied => Self::Applied,
1864            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::Draining => Self::Draining,
1865            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::Failed => Self::Failed,
1866            meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase::Forced => Self::Forced,
1867        }
1868    }
1869}
1870
1871impl From<ExternalToolSurfaceDeltaPhase>
1872    for meerkat_core::tool_scope::ExternalToolSurfaceDeltaPhase
1873{
1874    fn from(phase: ExternalToolSurfaceDeltaPhase) -> Self {
1875        match phase {
1876            ExternalToolSurfaceDeltaPhase::None => Self::None,
1877            ExternalToolSurfaceDeltaPhase::Pending => Self::Pending,
1878            ExternalToolSurfaceDeltaPhase::Applied => Self::Applied,
1879            ExternalToolSurfaceDeltaPhase::Draining => Self::Draining,
1880            ExternalToolSurfaceDeltaPhase::Failed => Self::Failed,
1881            ExternalToolSurfaceDeltaPhase::Forced => Self::Forced,
1882        }
1883    }
1884}
1885
1886/// Typed failure cause for an external tool surface. Closed mirror of
1887/// [`meerkat_core::tool_scope::ExternalToolSurfaceFailureCause`] so pending
1888/// failure and call-rejection causes cross the DSL as data, not string codes.
1889#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1890pub enum ExternalToolSurfaceFailureCause {
1891    #[default]
1892    PendingFailed,
1893    SurfaceDraining,
1894    SurfaceUnavailable,
1895}
1896
1897impl From<meerkat_core::tool_scope::ExternalToolSurfaceFailureCause>
1898    for ExternalToolSurfaceFailureCause
1899{
1900    fn from(cause: meerkat_core::tool_scope::ExternalToolSurfaceFailureCause) -> Self {
1901        match cause {
1902            meerkat_core::tool_scope::ExternalToolSurfaceFailureCause::PendingFailed => {
1903                Self::PendingFailed
1904            }
1905            meerkat_core::tool_scope::ExternalToolSurfaceFailureCause::SurfaceDraining => {
1906                Self::SurfaceDraining
1907            }
1908            meerkat_core::tool_scope::ExternalToolSurfaceFailureCause::SurfaceUnavailable => {
1909                Self::SurfaceUnavailable
1910            }
1911        }
1912    }
1913}
1914
1915impl From<ExternalToolSurfaceFailureCause>
1916    for meerkat_core::tool_scope::ExternalToolSurfaceFailureCause
1917{
1918    fn from(cause: ExternalToolSurfaceFailureCause) -> Self {
1919        match cause {
1920            ExternalToolSurfaceFailureCause::PendingFailed => Self::PendingFailed,
1921            ExternalToolSurfaceFailureCause::SurfaceDraining => Self::SurfaceDraining,
1922            ExternalToolSurfaceFailureCause::SurfaceUnavailable => Self::SurfaceUnavailable,
1923        }
1924    }
1925}
1926
1927/// Typed drain-exit reason. Closed mirror of
1928/// [`meerkat_core::handles::DrainExitReason`] — replaces the former
1929/// literal-string `reason` field on `NotifyDrainExited`.
1930#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1931pub enum DrainExitReason {
1932    #[default]
1933    IdleTimeout,
1934    Dismissed,
1935    Failed,
1936    Aborted,
1937    SessionShutdown,
1938}
1939
1940impl From<meerkat_core::handles::DrainExitReason> for DrainExitReason {
1941    fn from(reason: meerkat_core::handles::DrainExitReason) -> Self {
1942        match reason {
1943            meerkat_core::handles::DrainExitReason::IdleTimeout => Self::IdleTimeout,
1944            meerkat_core::handles::DrainExitReason::Dismissed => Self::Dismissed,
1945            meerkat_core::handles::DrainExitReason::Failed => Self::Failed,
1946            meerkat_core::handles::DrainExitReason::Aborted => Self::Aborted,
1947            meerkat_core::handles::DrainExitReason::SessionShutdown => Self::SessionShutdown,
1948        }
1949    }
1950}
1951
1952impl From<DrainExitReason> for meerkat_core::handles::DrainExitReason {
1953    fn from(reason: DrainExitReason) -> Self {
1954        match reason {
1955            DrainExitReason::IdleTimeout => Self::IdleTimeout,
1956            DrainExitReason::Dismissed => Self::Dismissed,
1957            DrainExitReason::Failed => Self::Failed,
1958            DrainExitReason::Aborted => Self::Aborted,
1959            DrainExitReason::SessionShutdown => Self::SessionShutdown,
1960        }
1961    }
1962}
1963
1964/// Typed work-lane origin for [`MeerkatMachineInput::Ingest`]. Closed set of
1965/// the work-lane labels the DSL observes on the admission seam — replaces
1966/// the former literal-string `origin` field. Structurally mirrors the
1967/// `MobMachine.RequestRuntimeIngress.origin` seam so the cross-machine
1968/// composition binds on a single typed enum instead of parallel
1969/// string-typed slots. Transport sources ([`meerkat_core::comms::InputSource`])
1970/// arriving from the shell side collapse to `External`; the
1971/// runtime-control-plane `Ingest` dispatch uses the dedicated `Ingest`
1972/// variant; mob-bridged ingress carries `External`/`Internal` matching
1973/// `meerkat-mob::ids::WorkOrigin`.
1974#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
1975pub enum WorkOrigin {
1976    #[default]
1977    External,
1978    Internal,
1979    /// Canonical admission entrypoint fired by the runtime control plane
1980    /// with no surface-level transport or work-lane label.
1981    Ingest,
1982}
1983
1984impl From<meerkat_core::comms::InputSource> for WorkOrigin {
1985    fn from(src: meerkat_core::comms::InputSource) -> Self {
1986        match src {
1987            // Transport-originated inputs are `External` work-lane: they
1988            // entered the runtime via a non-mob transport (TCP/UDS/stdin/
1989            // webhook/RPC). Mob-originated work fires the DSL directly
1990            // with `External`/`Internal` instead of going through the
1991            // session-admission handle.
1992            meerkat_core::comms::InputSource::Tcp
1993            | meerkat_core::comms::InputSource::Uds
1994            | meerkat_core::comms::InputSource::Stdin
1995            | meerkat_core::comms::InputSource::Webhook
1996            | meerkat_core::comms::InputSource::Rpc => Self::External,
1997        }
1998    }
1999}
2000
2001/// Typed async-operation lifecycle status. Closed mirror of
2002/// [`meerkat_core::ops_lifecycle::OperationStatus`] — replaces the former
2003/// literal-string values in the DSL's `op_statuses` map.
2004///
2005/// The DSL writes these variants directly on each ops lifecycle transition
2006/// (`RegisterOp`, `StartOp`, `CompleteOp`, `FailOp`, `CancelOp`, `AbortOp`,
2007/// `RetireRequestedOp`, `RetireCompletedOp`, `TerminateOp`). The shell's
2008/// `ShellState::status()` reads the typed value directly and maps to the
2009/// domain enum via the `From` impl below — no string compares, no string
2010/// parsing.
2011#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2012pub enum OperationStatus {
2013    #[default]
2014    Absent,
2015    Provisioning,
2016    Running,
2017    Retiring,
2018    Completed,
2019    Failed,
2020    Aborted,
2021    Cancelled,
2022    Retired,
2023    Terminated,
2024}
2025
2026impl From<meerkat_core::ops_lifecycle::OperationStatus> for OperationStatus {
2027    fn from(status: meerkat_core::ops_lifecycle::OperationStatus) -> Self {
2028        match status {
2029            meerkat_core::ops_lifecycle::OperationStatus::Absent => Self::Absent,
2030            meerkat_core::ops_lifecycle::OperationStatus::Provisioning => Self::Provisioning,
2031            meerkat_core::ops_lifecycle::OperationStatus::Running => Self::Running,
2032            meerkat_core::ops_lifecycle::OperationStatus::Retiring => Self::Retiring,
2033            meerkat_core::ops_lifecycle::OperationStatus::Completed => Self::Completed,
2034            meerkat_core::ops_lifecycle::OperationStatus::Failed => Self::Failed,
2035            meerkat_core::ops_lifecycle::OperationStatus::Aborted => Self::Aborted,
2036            meerkat_core::ops_lifecycle::OperationStatus::Cancelled => Self::Cancelled,
2037            meerkat_core::ops_lifecycle::OperationStatus::Retired => Self::Retired,
2038            meerkat_core::ops_lifecycle::OperationStatus::Terminated => Self::Terminated,
2039        }
2040    }
2041}
2042
2043impl From<OperationStatus> for meerkat_core::ops_lifecycle::OperationStatus {
2044    fn from(status: OperationStatus) -> Self {
2045        match status {
2046            OperationStatus::Absent => Self::Absent,
2047            OperationStatus::Provisioning => Self::Provisioning,
2048            OperationStatus::Running => Self::Running,
2049            OperationStatus::Retiring => Self::Retiring,
2050            OperationStatus::Completed => Self::Completed,
2051            OperationStatus::Failed => Self::Failed,
2052            OperationStatus::Aborted => Self::Aborted,
2053            OperationStatus::Cancelled => Self::Cancelled,
2054            OperationStatus::Retired => Self::Retired,
2055            OperationStatus::Terminated => Self::Terminated,
2056        }
2057    }
2058}
2059
2060/// Typed discriminant mirror of
2061/// [`meerkat_core::ops_lifecycle::OperationTerminalOutcome`] — replaces the
2062/// former opaque JSON string carried in the DSL's `op_terminal_outcomes`
2063/// map. Unit variants only; payload data (completion result, failure error,
2064/// cancellation reason, terminated reason) rides on the companion
2065/// `op_terminal_payload: Map<String, String>` field of the DSL state as
2066/// JSON keyed to the same operation id, and is reconstructed in the shell
2067/// by pairing the typed discriminant with the companion entry.
2068///
2069/// The DSL writes these variants directly on each terminal transition
2070/// (`CompleteOp`, `FailOp`, `CancelOp`, `AbortOp`, `RetireCompletedOp`,
2071/// `TerminateOp`); the shell reads them through the typed map and rebuilds
2072/// the domain enum in `ShellState::terminal_outcome`.
2073#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2074pub enum OperationTerminalOutcomeKind {
2075    #[default]
2076    Completed,
2077    Failed,
2078    Aborted,
2079    Cancelled,
2080    Retired,
2081    Terminated,
2082}
2083
2084/// Typed input-abandonment reason. Closed mirror of the discriminant set of
2085/// [`crate::input_state::InputAbandonReason`] — replaces the former
2086/// `format!("{reason:?}")` Debug round-trip in the DSL's
2087/// `input_abandon_reason` map.
2088///
2089/// The `MaxAttemptsExhausted` variant's `attempts` payload rides on the
2090/// companion `input_abandon_attempt_count: Map<String, u64>` field of the
2091/// DSL state; this enum only carries the discriminant. The domain
2092/// `InputAbandonReason::MaxAttemptsExhausted { attempts }` is reconstructed
2093/// in the driver by pairing the typed discriminant with that companion map.
2094#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2095pub enum InputAbandonReason {
2096    #[default]
2097    Retired,
2098    Reset,
2099    Stopped,
2100    Destroyed,
2101    Cancelled,
2102    MaxAttemptsExhausted,
2103}
2104
2105impl From<&crate::input_state::InputAbandonReason> for InputAbandonReason {
2106    fn from(reason: &crate::input_state::InputAbandonReason) -> Self {
2107        match reason {
2108            crate::input_state::InputAbandonReason::Retired => Self::Retired,
2109            crate::input_state::InputAbandonReason::Reset => Self::Reset,
2110            crate::input_state::InputAbandonReason::Stopped => Self::Stopped,
2111            crate::input_state::InputAbandonReason::Destroyed => Self::Destroyed,
2112            crate::input_state::InputAbandonReason::Cancelled => Self::Cancelled,
2113            crate::input_state::InputAbandonReason::MaxAttemptsExhausted { .. } => {
2114                Self::MaxAttemptsExhausted
2115            }
2116        }
2117    }
2118}
2119
2120impl InputAbandonReason {
2121    /// Stable lowercase label for event wire formats. Mirrors the
2122    /// snake-case serde representation of the domain enum for consistency
2123    /// with existing consumers.
2124    pub const fn as_str(self) -> &'static str {
2125        match self {
2126            Self::Retired => "retired",
2127            Self::Reset => "reset",
2128            Self::Stopped => "stopped",
2129            Self::Destroyed => "destroyed",
2130            Self::Cancelled => "cancelled",
2131            Self::MaxAttemptsExhausted => "max_attempts_exhausted",
2132        }
2133    }
2134}
2135
2136/// Typed work-lane assignment for admitted inputs. Replaces the former
2137/// parallel `queue_lane` / `steer_lane` sets with a single map
2138/// (`input_lane: Map<String, Enum<InputLane>>`) so mutual exclusion is
2139/// structural — an admitted input is in exactly one lane by construction.
2140///
2141/// DSL-side mirror of the shell's `meerkat_core::types::HandlingMode`; the
2142/// DSL owns the typed mirror so transitions can carry it without depending
2143/// on the shell's domain enum.
2144#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2145pub enum InputLane {
2146    #[default]
2147    Queue,
2148    Steer,
2149}
2150
2151impl From<crate::HandlingMode> for InputLane {
2152    fn from(mode: crate::HandlingMode) -> Self {
2153        match mode {
2154            crate::HandlingMode::Queue => Self::Queue,
2155            crate::HandlingMode::Steer => Self::Steer,
2156        }
2157    }
2158}
2159
2160#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2161pub enum RoutingSwitchTurnPhase {
2162    #[default]
2163    Requested,
2164    PendingForBoundary,
2165    ActiveFiniteOverride,
2166    ApplyingPersistentReconfigure,
2167    Terminal,
2168}
2169
2170#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2171pub enum RoutingSwitchTurnTerminal {
2172    #[default]
2173    Denied,
2174    ConsumedAndRestored,
2175    PersistentReconfigureApplied,
2176}
2177
2178#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2179pub enum RoutingDenialReason {
2180    #[default]
2181    CapabilityPolicy,
2182    ApprovalRequiredButUnavailable,
2183    DeniedDuringApproval,
2184    ScopedOverrideConflict,
2185    RealtimeTransportConflict,
2186}
2187
2188#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2189pub enum RoutingApprovalPhase {
2190    #[default]
2191    Pending,
2192    PresentedToUser,
2193    Approved,
2194    Denied,
2195    SurfaceDetached,
2196}
2197
2198#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2199pub enum RoutingApprovalParentKind {
2200    #[default]
2201    SwitchTurn,
2202    ImageOperation,
2203}
2204
2205#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2206pub enum RoutingImageOperationPhase {
2207    #[default]
2208    Requested,
2209    PlanResolved,
2210    ScopedOverrideActive,
2211    ProviderCallInFlight,
2212    ResultCommitted,
2213    RestoringScopedOverride,
2214    Terminal,
2215}
2216
2217#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2218pub enum RoutingImageTerminal {
2219    #[default]
2220    Generated,
2221    Denied,
2222    EmptyResult,
2223    RefusedByProvider,
2224    SafetyFiltered,
2225    Failed,
2226    Cancelled,
2227    Timeout,
2228    ScopedRestoreFailed,
2229}
2230
2231// Track-B (R5): declarative peer endpoint descriptor for the runtime
2232// DSL. Shape mirrors `meerkat_core::comms::TrustedPeerDescriptor`.
2233// The catalog DSL holds an identical type; the two are structurally
2234// equivalent so the schema validator sees consistent opaque struct
2235// shapes.
2236#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2237pub struct PeerEndpoint {
2238    pub name: PeerName,
2239    pub peer_id: PeerId,
2240    pub address: PeerAddress,
2241    pub signing_key: PeerSigningKey,
2242}
2243
2244impl PeerEndpoint {
2245    pub fn new(
2246        name: impl Into<PeerName>,
2247        peer_id: impl Into<PeerId>,
2248        address: impl Into<PeerAddress>,
2249        signing_key: impl Into<PeerSigningKey>,
2250    ) -> Self {
2251        Self {
2252            name: name.into(),
2253            peer_id: peer_id.into(),
2254            address: address.into(),
2255            signing_key: signing_key.into(),
2256        }
2257    }
2258}
2259
2260impl From<&meerkat_core::comms::TrustedPeerDescriptor> for PeerEndpoint {
2261    fn from(spec: &meerkat_core::comms::TrustedPeerDescriptor) -> Self {
2262        Self {
2263            name: PeerName(spec.name.as_str().to_owned()),
2264            peer_id: PeerId(spec.peer_id.to_string()),
2265            address: PeerAddress(spec.address.to_string()),
2266            signing_key: PeerSigningKey(spec.pubkey),
2267        }
2268    }
2269}
2270
2271/// DSL-local carrier for the Ed25519 public signing key associated with a
2272/// peer endpoint. The MeerkatMachine owns this projection alongside the
2273/// endpoint identity atoms so trust reconciliation can install the exact
2274/// key into the comms trust store without shell-side defaults.
2275#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2276pub struct PeerSigningKey(pub [u8; 32]);
2277
2278impl From<[u8; 32]> for PeerSigningKey {
2279    fn from(key: [u8; 32]) -> Self {
2280        Self(key)
2281    }
2282}
2283
2284/// DSL-local newtype for a peer display name. Wraps the slug string
2285/// so the schema validator sees a stable opaque shape; mirrors
2286/// `meerkat_core::comms::PeerName` but avoids dragging the core
2287/// comms types into the DSL grammar.
2288#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2289pub struct PeerName(pub String);
2290
2291impl<T: Into<String>> From<T> for PeerName {
2292    fn from(s: T) -> Self {
2293        Self(s.into())
2294    }
2295}
2296
2297impl PeerName {
2298    pub fn as_str(&self) -> &str {
2299        &self.0
2300    }
2301}
2302
2303/// DSL-local newtype for the canonical peer routing id.
2304#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2305pub struct PeerId(pub String);
2306
2307impl<T: Into<String>> From<T> for PeerId {
2308    fn from(s: T) -> Self {
2309        Self(s.into())
2310    }
2311}
2312
2313impl PeerId {
2314    pub fn as_str(&self) -> &str {
2315        &self.0
2316    }
2317}
2318
2319/// DSL-local newtype for a peer transport endpoint URL.
2320#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
2321pub struct PeerAddress(pub String);
2322
2323impl<T: Into<String>> From<T> for PeerAddress {
2324    fn from(s: T) -> Self {
2325        Self(s.into())
2326    }
2327}
2328
2329impl PeerAddress {
2330    pub fn as_str(&self) -> &str {
2331        &self.0
2332    }
2333}
2334
2335// Ensure we keep the exact generated schema DSL body from the catalog source.
2336
2337// MeerkatMachine production body is catalog-owned. Keep bridge/runtime mechanics
2338// outside this macro invocation; canonical semantics live in the catalog DSL.
2339meerkat_machine_schema::meerkat_catalog_machine_dsl!("meerkat-runtime", "meerkat_machine::dsl");
2340
2341// =====================================================================