meerkat_runtime/
ingress_types.rs1use meerkat_core::lifecycle::RuntimeExecutionKind;
10use meerkat_core::lifecycle::run_primitive::{
11 ConversationAppend, ConversationContextAppend, PeerResponseTerminalApplyIntent,
12 RunApplyBoundary,
13};
14use meerkat_core::types::HandlingMode;
15use serde::{Deserialize, Serialize};
16
17use crate::identifiers::{InputKind, KindId};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
24pub struct ContentShape(InputKind);
25
26impl ContentShape {
27 pub const fn from_kind(kind: InputKind) -> Self {
28 Self(kind)
29 }
30
31 pub const fn from_kind_id(kind_id: KindId) -> Self {
32 Self(kind_id.kind())
33 }
34
35 pub const fn kind(self) -> InputKind {
36 self.0
37 }
38
39 pub fn as_str(self) -> &'static str {
40 self.0.as_str()
41 }
42}
43
44impl From<InputKind> for ContentShape {
45 fn from(kind: InputKind) -> Self {
46 Self::from_kind(kind)
47 }
48}
49
50impl From<KindId> for ContentShape {
51 fn from(kind_id: KindId) -> Self {
52 Self::from_kind_id(kind_id)
53 }
54}
55
56impl std::fmt::Display for ContentShape {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 f.write_str(self.as_str())
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64pub struct ReservationKey(pub String);
65
66#[derive(Debug, Clone, PartialEq, Eq, Hash)]
68pub struct RequestId(pub String);
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77pub struct RuntimeInputSemantics {
78 pub(crate) boundary: RunApplyBoundary,
79 pub(crate) execution_kind: RuntimeExecutionKind,
80 pub(crate) execution_handling_mode: Option<HandlingMode>,
81 pub(crate) peer_response_terminal_apply_intent: Option<PeerResponseTerminalApplyIntent>,
82 #[serde(default)]
87 pub(crate) live_interrupt_required: bool,
88}
89
90#[derive(Debug, Clone, Default, PartialEq)]
99pub struct RuntimeInputProjection {
100 pub injected_context_appends: Vec<ConversationAppend>,
106 pub append: Option<ConversationAppend>,
107 pub additional_appends: Vec<ConversationAppend>,
108 pub context_append: Option<ConversationContextAppend>,
109 pub peer_response_terminal: Option<meerkat_core::PeerResponseTerminalFact>,
115}
116
117impl RuntimeInputSemantics {
118 pub fn try_from_generated_admission(
119 input: &crate::input::Input,
120 runtime_idle: bool,
121 ) -> Result<Self, String> {
122 crate::policy_table::generated_admission_projection_for_input(input, runtime_idle)
123 .map(|projection| projection.runtime_semantics)
124 }
125
126 pub fn boundary(&self) -> RunApplyBoundary {
127 self.boundary
128 }
129
130 pub fn execution_kind(&self) -> RuntimeExecutionKind {
131 self.execution_kind
132 }
133
134 pub fn peer_response_terminal_apply_intent(&self) -> Option<PeerResponseTerminalApplyIntent> {
135 self.peer_response_terminal_apply_intent
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn terminal_peer_response_keeps_content_turn_execution_kind() {
145 let semantics = crate::policy_table::generated_admission_projection_for_kind(
146 KindId::new(InputKind::PeerResponseTerminal),
147 false,
148 )
149 .expect("generated admission projection")
150 .runtime_semantics;
151
152 assert_eq!(semantics.boundary, RunApplyBoundary::RunStart);
153 assert_eq!(semantics.execution_kind, RuntimeExecutionKind::ContentTurn);
154 assert_eq!(semantics.execution_handling_mode, None);
155 assert_eq!(
156 semantics.peer_response_terminal_apply_intent,
157 Some(PeerResponseTerminalApplyIntent::AppendContextAndRun)
158 );
159 }
160
161 #[test]
162 fn continuation_is_the_only_resume_pending_execution_kind() {
163 let semantics = crate::policy_table::generated_admission_projection_for_kind(
164 KindId::new(InputKind::Continuation),
165 false,
166 )
167 .expect("generated admission projection")
168 .runtime_semantics;
169
170 assert_eq!(semantics.boundary, RunApplyBoundary::RunCheckpoint);
171 assert_eq!(
172 semantics.execution_kind,
173 RuntimeExecutionKind::ResumePending
174 );
175 assert_eq!(semantics.execution_handling_mode, None);
176 assert_eq!(semantics.peer_response_terminal_apply_intent, None);
177 }
178
179 #[test]
180 fn admitted_content_shape_is_closed_to_input_kind_contract() {
181 let shapes = [
182 (InputKind::Prompt, "prompt"),
183 (InputKind::PeerMessage, "peer_message"),
184 (InputKind::PeerRequest, "peer_request"),
185 (InputKind::PeerResponseProgress, "peer_response_progress"),
186 (InputKind::PeerResponseTerminal, "peer_response_terminal"),
187 (InputKind::FlowStep, "flow_step"),
188 (InputKind::ExternalEvent, "external_event"),
189 (InputKind::Continuation, "continuation"),
190 (InputKind::Operation, "operation"),
191 ];
192
193 for (kind, label) in shapes {
194 let shape = ContentShape::from_kind(kind);
195 assert_eq!(shape.kind(), kind);
196 assert_eq!(shape.as_str(), label);
197 assert_eq!(shape.to_string(), label);
198 }
199 }
200
201 #[test]
202 fn admitted_content_shape_source_has_no_string_newtype_contract() {
203 let source = std::fs::read_to_string(
204 std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
205 .join("src")
206 .join("ingress_types.rs"),
207 )
208 .expect("read ingress types source");
209
210 let forbidden = ["pub struct ContentShape", "(pub String)"].concat();
211 assert!(
212 !source.contains(&forbidden),
213 "runtime admitted-input ContentShape must not be a public arbitrary string newtype"
214 );
215 }
216}