use meerkat_core::lifecycle::RuntimeExecutionKind;
use meerkat_core::lifecycle::run_primitive::{
ConversationAppend, ConversationContextAppend, PeerResponseTerminalApplyIntent,
RunApplyBoundary,
};
use serde::{Deserialize, Serialize};
use crate::identifiers::{InputKind, KindId};
use crate::input::Input;
use crate::policy::{ApplyMode, PolicyDecision};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ContentShape(InputKind);
impl ContentShape {
pub const fn from_kind(kind: InputKind) -> Self {
Self(kind)
}
pub const fn from_kind_id(kind_id: KindId) -> Self {
Self(kind_id.kind())
}
pub const fn kind(self) -> InputKind {
self.0
}
pub fn as_str(self) -> &'static str {
self.0.as_str()
}
}
impl From<InputKind> for ContentShape {
fn from(kind: InputKind) -> Self {
Self::from_kind(kind)
}
}
impl From<KindId> for ContentShape {
fn from(kind_id: KindId) -> Self {
Self::from_kind_id(kind_id)
}
}
impl std::fmt::Display for ContentShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct ReservationKey(pub String);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct RequestId(pub String);
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuntimeInputSemantics {
pub boundary: RunApplyBoundary,
pub execution_kind: RuntimeExecutionKind,
pub peer_response_terminal_apply_intent: Option<PeerResponseTerminalApplyIntent>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct RuntimeInputProjection {
pub append: Option<ConversationAppend>,
pub additional_appends: Vec<ConversationAppend>,
pub context_append: Option<ConversationContextAppend>,
}
impl RuntimeInputSemantics {
fn boundary_from_policy(policy: &PolicyDecision) -> RunApplyBoundary {
match policy.apply_mode {
ApplyMode::StageRunBoundary => RunApplyBoundary::RunCheckpoint,
ApplyMode::InjectNow => RunApplyBoundary::Immediate,
ApplyMode::StageRunStart | ApplyMode::Ignore => RunApplyBoundary::RunStart,
}
}
pub fn from_policy_and_execution_kind(
policy: &PolicyDecision,
execution_kind: RuntimeExecutionKind,
peer_response_terminal_apply_intent: Option<PeerResponseTerminalApplyIntent>,
) -> Self {
Self {
boundary: Self::boundary_from_policy(policy),
execution_kind,
peer_response_terminal_apply_intent,
}
}
pub fn from_policy_and_kind(policy: &PolicyDecision, kind: InputKind) -> Self {
let execution_kind = match kind {
InputKind::Continuation => RuntimeExecutionKind::ResumePending,
InputKind::Prompt
| InputKind::PeerMessage
| InputKind::PeerRequest
| InputKind::PeerResponseProgress
| InputKind::PeerResponseTerminal
| InputKind::FlowStep
| InputKind::ExternalEvent
| InputKind::Operation => RuntimeExecutionKind::ContentTurn,
};
let peer_response_terminal_apply_intent = match kind {
InputKind::PeerResponseTerminal => {
Some(PeerResponseTerminalApplyIntent::AppendContextAndRun)
}
InputKind::Prompt
| InputKind::PeerMessage
| InputKind::PeerRequest
| InputKind::PeerResponseProgress
| InputKind::FlowStep
| InputKind::ExternalEvent
| InputKind::Continuation
| InputKind::Operation => None,
};
Self::from_policy_and_execution_kind(
policy,
execution_kind,
peer_response_terminal_apply_intent,
)
}
pub fn from_policy_and_input(policy: &PolicyDecision, input: &Input) -> Self {
let mut semantics = Self::from_policy_and_kind(policy, input.kind());
if let Input::Continuation(continuation) = input
&& continuation.turn_append.is_some()
{
semantics.execution_kind = RuntimeExecutionKind::ContentTurn;
}
semantics
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::{ConsumePoint, DrainPolicy, QueueMode, RoutingDisposition, WakeMode};
fn policy(apply_mode: ApplyMode) -> PolicyDecision {
PolicyDecision {
apply_mode,
wake_mode: WakeMode::WakeIfIdle,
queue_mode: QueueMode::Fifo,
consume_point: ConsumePoint::OnRunComplete,
drain_policy: DrainPolicy::QueueNextTurn,
routing_disposition: RoutingDisposition::Queue,
record_transcript: true,
emit_operator_content: true,
policy_version: crate::policy_table::DEFAULT_POLICY_VERSION,
}
}
#[test]
fn terminal_peer_response_keeps_content_turn_execution_kind() {
let semantics = RuntimeInputSemantics::from_policy_and_kind(
&policy(ApplyMode::StageRunBoundary),
InputKind::PeerResponseTerminal,
);
assert_eq!(semantics.boundary, RunApplyBoundary::RunCheckpoint);
assert_eq!(semantics.execution_kind, RuntimeExecutionKind::ContentTurn);
assert_eq!(
semantics.peer_response_terminal_apply_intent,
Some(PeerResponseTerminalApplyIntent::AppendContextAndRun)
);
}
#[test]
fn continuation_is_the_only_resume_pending_execution_kind() {
let semantics = RuntimeInputSemantics::from_policy_and_kind(
&policy(ApplyMode::StageRunBoundary),
InputKind::Continuation,
);
assert_eq!(semantics.boundary, RunApplyBoundary::RunCheckpoint);
assert_eq!(
semantics.execution_kind,
RuntimeExecutionKind::ResumePending
);
assert_eq!(semantics.peer_response_terminal_apply_intent, None);
}
#[test]
fn admitted_content_shape_is_closed_to_input_kind_contract() {
let shapes = [
(InputKind::Prompt, "prompt"),
(InputKind::PeerMessage, "peer_message"),
(InputKind::PeerRequest, "peer_request"),
(InputKind::PeerResponseProgress, "peer_response_progress"),
(InputKind::PeerResponseTerminal, "peer_response_terminal"),
(InputKind::FlowStep, "flow_step"),
(InputKind::ExternalEvent, "external_event"),
(InputKind::Continuation, "continuation"),
(InputKind::Operation, "operation"),
];
for (kind, label) in shapes {
let shape = ContentShape::from_kind(kind);
assert_eq!(shape.kind(), kind);
assert_eq!(shape.as_str(), label);
assert_eq!(shape.to_string(), label);
}
}
#[test]
fn admitted_content_shape_source_has_no_string_newtype_contract() {
let source = std::fs::read_to_string(
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("src")
.join("ingress_types.rs"),
)
.expect("read ingress types source");
let forbidden = ["pub struct ContentShape", "(pub String)"].concat();
assert!(
!source.contains(&forbidden),
"runtime admitted-input ContentShape must not be a public arbitrary string newtype"
);
}
}