meerkat_runtime/
ingress_types.rs1use 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#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
63pub struct ReservationKey(pub String);
64
65#[derive(Debug, Clone, PartialEq, Eq, Hash)]
67pub struct RequestId(pub String);
68
69#[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 #[serde(default)]
86 pub(crate) live_interrupt_required: bool,
87}
88
89#[derive(Debug, Clone, Default, PartialEq)]
98pub struct RuntimeInputProjection {
99 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}