Skip to main content

meerkat_runtime/
ingress_types.rs

1//! Wire-type shells preserved from the deleted runtime ingress authority.
2//!
3//! These types name admission metadata that persists beyond the authority
4//! itself. They are pure data — no authority methods, no shadow state. The
5//! DSL owns ingress semantics (queue lanes, input phases, admission
6//! ordering); these types just carry content-shape / correlation metadata
7//! from the admission point to observability readers.
8
9use meerkat_core::lifecycle::RuntimeExecutionKind;
10use meerkat_core::lifecycle::run_primitive::{
11    ConversationAppend, PeerResponseTerminalApplyIntent, RunApplyBoundary,
12};
13use meerkat_core::types::HandlingMode;
14use serde::{Deserialize, Serialize};
15
16use crate::identifiers::{InputKind, KindId};
17
18/// Content shape classification for admitted inputs.
19///
20/// Used by the admitted-input snapshot surface so callers can correlate
21/// admissions by content type without re-parsing the Input payload.
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23pub struct ContentShape(InputKind);
24
25impl ContentShape {
26    pub const fn from_kind(kind: InputKind) -> Self {
27        Self(kind)
28    }
29
30    pub const fn from_kind_id(kind_id: KindId) -> Self {
31        Self(kind_id.kind())
32    }
33
34    pub const fn kind(self) -> InputKind {
35        self.0
36    }
37
38    pub fn as_str(self) -> &'static str {
39        self.0.as_str()
40    }
41}
42
43impl From<InputKind> for ContentShape {
44    fn from(kind: InputKind) -> Self {
45        Self::from_kind(kind)
46    }
47}
48
49impl From<KindId> for ContentShape {
50    fn from(kind_id: KindId) -> Self {
51        Self::from_kind_id(kind_id)
52    }
53}
54
55impl std::fmt::Display for ContentShape {
56    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57        f.write_str(self.as_str())
58    }
59}
60
61/// Reservation key for admitted inputs.
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub struct ReservationKey(pub String);
64
65/// Request ID for correlation tracking.
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub struct RequestId(pub String);
68
69/// Machine-owned runtime-loop semantics captured at admission.
70///
71/// The runtime loop must not re-read peer conventions, continuation payloads,
72/// or handling-mode hints to decide how a dequeued input runs. Admission has
73/// already resolved the typed policy/kind tuple; this record is the canonical
74/// carrier from that decision point to `RunPrimitive` construction.
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
76pub struct RuntimeInputSemantics {
77    pub(crate) boundary: RunApplyBoundary,
78    pub(crate) execution_kind: RuntimeExecutionKind,
79    pub(crate) execution_handling_mode: Option<HandlingMode>,
80    pub(crate) peer_response_terminal_apply_intent: Option<PeerResponseTerminalApplyIntent>,
81    /// #338: machine-owned verdict that this admitted input requires a live
82    /// channel interrupt (true iff the admitted lane is `Steer`). Carried
83    /// end-to-end so the live-projection consumer reads the typed fact instead
84    /// of re-scanning `handling_mode == Steer`.
85    #[serde(default)]
86    pub(crate) live_interrupt_required: bool,
87}
88
89/// Admitted conversation projection for one input.
90///
91/// The raw input payload is still retained for durability/replay, but the
92/// runtime loop consumes this admitted projection when constructing
93/// `RunPrimitive` so dequeue mechanics do not reinterpret peer conventions or
94/// terminal status payloads.
95// Cannot derive `Eq`: `peer_response_terminal` carries a typed fact whose
96// render payload is a `serde_json::Value`, which is `PartialEq` but not `Eq`.
97#[derive(Debug, Clone, Default, PartialEq)]
98pub struct RuntimeInputProjection {
99    /// Host-attached injected-context appends staged immediately BEFORE
100    /// `append` (the input's own transcript append) when the batch primitive
101    /// is constructed. A distinct slot — not `additional_appends` — because
102    /// the ordering invariant is "injected context lands before the turn's
103    /// user/peer append" and `additional_appends` chain AFTER it.
104    pub injected_context_appends: Vec<ConversationAppend>,
105    pub append: Option<ConversationAppend>,
106    pub additional_appends: Vec<ConversationAppend>,
107}
108
109impl RuntimeInputSemantics {
110    pub fn try_from_generated_admission(
111        input: &crate::input::Input,
112        runtime_idle: bool,
113    ) -> Result<Self, String> {
114        crate::policy_table::generated_admission_projection_for_input(input, runtime_idle)
115            .map(|projection| projection.runtime_semantics)
116    }
117
118    pub fn boundary(&self) -> RunApplyBoundary {
119        self.boundary
120    }
121
122    pub fn execution_kind(&self) -> RuntimeExecutionKind {
123        self.execution_kind
124    }
125
126    pub fn peer_response_terminal_apply_intent(&self) -> Option<PeerResponseTerminalApplyIntent> {
127        self.peer_response_terminal_apply_intent
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use super::*;
134
135    #[test]
136    fn terminal_peer_response_keeps_content_turn_execution_kind() {
137        let semantics = crate::policy_table::generated_admission_projection_for_kind(
138            KindId::new(InputKind::PeerResponseTerminal),
139            false,
140        )
141        .expect("generated admission projection")
142        .runtime_semantics;
143
144        assert_eq!(semantics.boundary, RunApplyBoundary::RunStart);
145        assert_eq!(semantics.execution_kind, RuntimeExecutionKind::ContentTurn);
146        assert_eq!(semantics.execution_handling_mode, None);
147        assert_eq!(
148            semantics.peer_response_terminal_apply_intent,
149            Some(PeerResponseTerminalApplyIntent::AppendContentAndRun)
150        );
151    }
152
153    #[test]
154    fn continuation_is_the_only_resume_pending_execution_kind() {
155        let semantics = crate::policy_table::generated_admission_projection_for_kind(
156            KindId::new(InputKind::Continuation),
157            false,
158        )
159        .expect("generated admission projection")
160        .runtime_semantics;
161
162        assert_eq!(semantics.boundary, RunApplyBoundary::RunCheckpoint);
163        assert_eq!(
164            semantics.execution_kind,
165            RuntimeExecutionKind::ResumePending
166        );
167        assert_eq!(semantics.execution_handling_mode, None);
168        assert_eq!(semantics.peer_response_terminal_apply_intent, None);
169    }
170
171    #[test]
172    fn admitted_content_shape_is_closed_to_input_kind_contract() {
173        let shapes = [
174            (InputKind::Prompt, "prompt"),
175            (InputKind::PeerMessage, "peer_message"),
176            (InputKind::PeerRequest, "peer_request"),
177            (InputKind::PeerResponseProgress, "peer_response_progress"),
178            (InputKind::PeerResponseTerminal, "peer_response_terminal"),
179            (InputKind::FlowStep, "flow_step"),
180            (InputKind::ExternalEvent, "external_event"),
181            (InputKind::Continuation, "continuation"),
182            (InputKind::Operation, "operation"),
183        ];
184
185        for (kind, label) in shapes {
186            let shape = ContentShape::from_kind(kind);
187            assert_eq!(shape.kind(), kind);
188            assert_eq!(shape.as_str(), label);
189            assert_eq!(shape.to_string(), label);
190        }
191    }
192
193    #[test]
194    fn admitted_content_shape_source_has_no_string_newtype_contract() {
195        let source = std::fs::read_to_string(
196            std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
197                .join("src")
198                .join("ingress_types.rs"),
199        )
200        .expect("read ingress types source");
201
202        let forbidden = ["pub struct ContentShape", "(pub String)"].concat();
203        assert!(
204            !source.contains(&forbidden),
205            "runtime admitted-input ContentShape must not be a public arbitrary string newtype"
206        );
207    }
208}