use super::AppState;
use super::core;
use super::decomposition::DelegationProvenance;
#[cfg(test)]
use super::guard_registry::{GuardChain, guard_sets};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum GuardSetPreset {
Full,
Cached,
Streaming,
None,
}
impl GuardSetPreset {
#[cfg(test)]
pub fn resolve(self) -> GuardChain {
match self {
Self::Full => guard_sets::full(),
Self::Cached => guard_sets::cached(),
Self::Streaming => guard_sets::streaming(),
Self::None => GuardChain::empty(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum SessionResolutionMode {
FromBody,
FromChannel {
platform: String,
},
Dedicated,
#[cfg(test)]
Provided { session_id: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) enum AuthorityMode {
ApiClaim,
ChannelClaim,
SelfGenerated,
AuditOnly,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum InferenceMode {
Standard,
Streaming,
}
#[derive(Debug, Clone)]
#[allow(dead_code)] pub(super) struct PipelineConfig {
pub injection_defense: bool,
pub dedup_tracking: bool,
pub session_resolution: SessionResolutionMode,
pub decomposition_gate: bool,
pub delegated_execution: bool,
pub specialist_controls: bool,
pub shortcuts_enabled: bool,
pub inference_mode: InferenceMode,
pub guard_set: GuardSetPreset,
pub cache_guard_set: GuardSetPreset,
pub cache_enabled: bool,
pub authority_mode: AuthorityMode,
pub post_turn_ingest: bool,
pub nickname_refinement: bool,
pub inject_diagnostics: bool,
pub channel_label: String,
}
impl PipelineConfig {
pub fn api() -> Self {
Self {
injection_defense: true,
dedup_tracking: true,
session_resolution: SessionResolutionMode::FromBody,
decomposition_gate: true,
delegated_execution: true,
specialist_controls: false,
shortcuts_enabled: true,
inference_mode: InferenceMode::Standard,
guard_set: GuardSetPreset::Full,
cache_guard_set: GuardSetPreset::Cached,
cache_enabled: true,
authority_mode: AuthorityMode::ApiClaim,
post_turn_ingest: true,
nickname_refinement: true,
inject_diagnostics: true,
channel_label: "api".into(),
}
}
pub fn streaming() -> Self {
Self {
injection_defense: true,
dedup_tracking: true,
session_resolution: SessionResolutionMode::FromBody,
decomposition_gate: false,
delegated_execution: false,
specialist_controls: false,
shortcuts_enabled: false,
inference_mode: InferenceMode::Streaming,
guard_set: GuardSetPreset::Streaming,
cache_guard_set: GuardSetPreset::None,
cache_enabled: true, authority_mode: AuthorityMode::AuditOnly,
post_turn_ingest: true,
nickname_refinement: false,
inject_diagnostics: true,
channel_label: "api-stream".into(),
}
}
pub fn channel(platform: &str) -> Self {
Self {
injection_defense: true,
dedup_tracking: true,
session_resolution: SessionResolutionMode::FromChannel {
platform: platform.to_string(),
},
decomposition_gate: true,
delegated_execution: true,
specialist_controls: true,
shortcuts_enabled: true,
inference_mode: InferenceMode::Standard,
guard_set: GuardSetPreset::Full,
cache_guard_set: GuardSetPreset::Cached,
cache_enabled: true,
authority_mode: AuthorityMode::ChannelClaim,
post_turn_ingest: true,
nickname_refinement: false,
inject_diagnostics: false,
channel_label: platform.to_string(),
}
}
pub fn cron() -> Self {
Self {
injection_defense: true, dedup_tracking: false,
session_resolution: SessionResolutionMode::Dedicated,
decomposition_gate: true,
delegated_execution: false, specialist_controls: false,
shortcuts_enabled: true,
inference_mode: InferenceMode::Standard,
guard_set: GuardSetPreset::Full,
cache_guard_set: GuardSetPreset::Cached,
cache_enabled: true,
authority_mode: AuthorityMode::SelfGenerated,
post_turn_ingest: true,
nickname_refinement: false,
inject_diagnostics: false,
channel_label: "cron".into(),
}
}
}
#[cfg(test)]
impl PipelineConfig {
pub fn is_standard_inference(&self) -> bool {
self.inference_mode == InferenceMode::Standard
}
pub fn is_streaming_inference(&self) -> bool {
self.inference_mode == InferenceMode::Streaming
}
pub fn enforces_authority(&self) -> bool {
matches!(
self.authority_mode,
AuthorityMode::ApiClaim | AuthorityMode::ChannelClaim
)
}
pub fn can_execute_tools(&self) -> bool {
self.inference_mode == InferenceMode::Standard
}
pub fn resolves_session_from_body(&self) -> bool {
matches!(self.session_resolution, SessionResolutionMode::FromBody)
}
pub fn is_channel(&self) -> bool {
matches!(
self.session_resolution,
SessionResolutionMode::FromChannel { .. }
)
}
pub fn is_cron(&self) -> bool {
matches!(self.session_resolution, SessionResolutionMode::Dedicated)
&& matches!(self.authority_mode, AuthorityMode::SelfGenerated)
}
}
pub(super) struct UnifiedPipelineInput<'a> {
pub state: &'a AppState,
pub config: &'a PipelineConfig,
pub session_id: &'a str,
pub user_content: &'a str,
pub turn_id: &'a str,
pub is_correction_turn: bool,
pub delegation_workflow_note: Option<String>,
pub gate_system_note: Option<String>,
pub delegated_execution_note: Option<String>,
pub delegation_provenance: DelegationProvenance,
}
pub(super) async fn execute_unified_pipeline(
input: UnifiedPipelineInput<'_>,
) -> Result<core::PipelineResult, String> {
let config = input.state.config.read().await;
let agent_name = config.agent.name.clone();
let agent_id = config.agent.id.clone();
let primary_model = config.models.primary.clone();
let tier_adapt = config.tier_adapt.clone();
drop(config);
let personality = input.state.personality.read().await;
let os_text = personality.os_text.clone();
let firmware_text = personality.firmware_text.clone();
drop(personality);
let inference_input = core::InferenceInput {
state: input.state,
session_id: input.session_id,
user_content: input.user_content,
turn_id: input.turn_id,
channel_label: &input.config.channel_label,
agent_name,
agent_id,
os_text,
firmware_text,
primary_model,
tier_adapt,
delegation_workflow_note: input.delegation_workflow_note,
inject_diagnostics: input.config.inject_diagnostics,
gate_system_note: input.gate_system_note,
delegated_execution_note: input.delegated_execution_note,
is_correction_turn: input.is_correction_turn,
};
let prepared = core::prepare_inference(&inference_input).await?;
let authority = match input.config.authority_mode {
AuthorityMode::SelfGenerated => ironclad_core::InputAuthority::SelfGenerated,
AuthorityMode::AuditOnly => ironclad_core::InputAuthority::SelfGenerated,
AuthorityMode::ApiClaim | AuthorityMode::ChannelClaim => {
tracing::warn!(
mode = ?input.config.authority_mode,
"execute_unified_pipeline called with claim-based authority — \
caller should resolve authority before calling"
);
ironclad_core::InputAuthority::SelfGenerated
}
};
let mut provenance = input.delegation_provenance;
core::execute_inference_pipeline(
input.state,
&prepared,
input.session_id,
input.user_content,
input.turn_id,
authority,
Some(&input.config.channel_label),
&mut provenance,
)
.await
}
pub(super) async fn execute_unified_pipeline_with_authority(
input: UnifiedPipelineInput<'_>,
authority: ironclad_core::InputAuthority,
) -> Result<core::PipelineResult, String> {
let config = input.state.config.read().await;
let agent_name = config.agent.name.clone();
let agent_id = config.agent.id.clone();
let primary_model = config.models.primary.clone();
let tier_adapt = config.tier_adapt.clone();
drop(config);
let personality = input.state.personality.read().await;
let os_text = personality.os_text.clone();
let firmware_text = personality.firmware_text.clone();
drop(personality);
let inference_input = core::InferenceInput {
state: input.state,
session_id: input.session_id,
user_content: input.user_content,
turn_id: input.turn_id,
channel_label: &input.config.channel_label,
agent_name,
agent_id,
os_text,
firmware_text,
primary_model,
tier_adapt,
delegation_workflow_note: input.delegation_workflow_note,
inject_diagnostics: input.config.inject_diagnostics,
gate_system_note: input.gate_system_note,
delegated_execution_note: input.delegated_execution_note,
is_correction_turn: input.is_correction_turn,
};
let prepared = core::prepare_inference(&inference_input).await?;
let mut provenance = input.delegation_provenance;
core::execute_inference_pipeline(
input.state,
&prepared,
input.session_id,
input.user_content,
input.turn_id,
authority,
Some(&input.config.channel_label),
&mut provenance,
)
.await
}
pub(super) async fn prepare_unified_pipeline(
input: &UnifiedPipelineInput<'_>,
) -> Result<core::PreparedInference, String> {
let config = input.state.config.read().await;
let agent_name = config.agent.name.clone();
let agent_id = config.agent.id.clone();
let primary_model = config.models.primary.clone();
let tier_adapt = config.tier_adapt.clone();
drop(config);
let personality = input.state.personality.read().await;
let os_text = personality.os_text.clone();
let firmware_text = personality.firmware_text.clone();
drop(personality);
let inference_input = core::InferenceInput {
state: input.state,
session_id: input.session_id,
user_content: input.user_content,
turn_id: input.turn_id,
channel_label: &input.config.channel_label,
agent_name,
agent_id,
os_text,
firmware_text,
primary_model,
tier_adapt,
delegation_workflow_note: input.delegation_workflow_note.clone(),
inject_diagnostics: input.config.inject_diagnostics,
gate_system_note: input.gate_system_note.clone(),
delegated_execution_note: input.delegated_execution_note.clone(),
is_correction_turn: input.is_correction_turn,
};
core::prepare_inference(&inference_input).await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn api_preset_enables_all_core_features() {
let cfg = PipelineConfig::api();
assert!(cfg.injection_defense);
assert!(cfg.dedup_tracking);
assert!(cfg.decomposition_gate);
assert!(cfg.delegated_execution);
assert!(cfg.shortcuts_enabled);
assert!(cfg.cache_enabled);
assert!(cfg.post_turn_ingest);
assert!(cfg.nickname_refinement);
assert!(cfg.inject_diagnostics);
assert!(!cfg.specialist_controls); assert_eq!(cfg.inference_mode, InferenceMode::Standard);
assert_eq!(cfg.guard_set, GuardSetPreset::Full);
assert_eq!(cfg.cache_guard_set, GuardSetPreset::Cached);
assert_eq!(cfg.authority_mode, AuthorityMode::ApiClaim);
assert_eq!(cfg.channel_label, "api");
assert_eq!(cfg.session_resolution, SessionResolutionMode::FromBody);
}
#[test]
fn streaming_preset_disables_react_features() {
let cfg = PipelineConfig::streaming();
assert!(cfg.injection_defense);
assert!(cfg.dedup_tracking);
assert!(!cfg.decomposition_gate);
assert!(!cfg.delegated_execution);
assert!(!cfg.shortcuts_enabled);
assert!(!cfg.specialist_controls);
assert_eq!(cfg.inference_mode, InferenceMode::Streaming);
assert_eq!(cfg.guard_set, GuardSetPreset::Streaming);
assert_eq!(cfg.cache_guard_set, GuardSetPreset::None);
assert!(!cfg.nickname_refinement);
assert!(cfg.post_turn_ingest);
assert!(cfg.cache_enabled);
assert_eq!(cfg.authority_mode, AuthorityMode::AuditOnly);
assert_eq!(cfg.channel_label, "api-stream");
}
#[test]
fn channel_preset_enables_specialist_controls() {
let cfg = PipelineConfig::channel("telegram");
assert!(cfg.injection_defense);
assert!(cfg.dedup_tracking);
assert!(cfg.decomposition_gate);
assert!(cfg.delegated_execution);
assert!(cfg.shortcuts_enabled);
assert!(cfg.specialist_controls); assert!(cfg.cache_enabled);
assert!(cfg.post_turn_ingest);
assert!(!cfg.nickname_refinement); assert!(!cfg.inject_diagnostics); assert_eq!(cfg.inference_mode, InferenceMode::Standard);
assert_eq!(cfg.guard_set, GuardSetPreset::Full);
assert_eq!(cfg.cache_guard_set, GuardSetPreset::Cached);
assert_eq!(cfg.authority_mode, AuthorityMode::ChannelClaim);
assert_eq!(cfg.channel_label, "telegram");
assert_eq!(
cfg.session_resolution,
SessionResolutionMode::FromChannel {
platform: "telegram".into()
}
);
}
#[test]
fn channel_preset_uses_platform_as_label() {
let telegram = PipelineConfig::channel("telegram");
assert_eq!(telegram.channel_label, "telegram");
let discord = PipelineConfig::channel("discord");
assert_eq!(discord.channel_label, "discord");
let email = PipelineConfig::channel("email");
assert_eq!(email.channel_label, "email");
}
#[test]
fn cron_preset_has_injection_defense() {
let cfg = PipelineConfig::cron();
assert!(cfg.injection_defense);
assert!(!cfg.dedup_tracking);
assert!(cfg.decomposition_gate);
assert!(!cfg.delegated_execution); assert!(cfg.shortcuts_enabled);
assert!(!cfg.specialist_controls);
assert!(cfg.cache_enabled);
assert!(cfg.post_turn_ingest);
assert!(!cfg.nickname_refinement);
assert!(!cfg.inject_diagnostics);
assert_eq!(cfg.inference_mode, InferenceMode::Standard);
assert_eq!(cfg.guard_set, GuardSetPreset::Full);
assert_eq!(cfg.cache_guard_set, GuardSetPreset::Cached);
assert_eq!(cfg.authority_mode, AuthorityMode::SelfGenerated);
assert_eq!(cfg.channel_label, "cron");
assert_eq!(cfg.session_resolution, SessionResolutionMode::Dedicated);
}
#[test]
fn guard_set_presets_resolve_to_non_empty_chains() {
let full = GuardSetPreset::Full.resolve();
assert!(!full.is_empty());
let cached = GuardSetPreset::Cached.resolve();
assert!(!cached.is_empty());
let streaming = GuardSetPreset::Streaming.resolve();
assert!(!streaming.is_empty());
}
#[test]
fn guard_set_none_resolves_to_empty_chain() {
let none = GuardSetPreset::None.resolve();
assert!(none.is_empty());
}
#[test]
fn api_predicates() {
let cfg = PipelineConfig::api();
assert!(cfg.is_standard_inference());
assert!(!cfg.is_streaming_inference());
assert!(cfg.enforces_authority());
assert!(cfg.can_execute_tools());
assert!(cfg.resolves_session_from_body());
assert!(!cfg.is_channel());
assert!(!cfg.is_cron());
}
#[test]
fn streaming_predicates() {
let cfg = PipelineConfig::streaming();
assert!(!cfg.is_standard_inference());
assert!(cfg.is_streaming_inference());
assert!(!cfg.enforces_authority());
assert!(!cfg.can_execute_tools());
assert!(cfg.resolves_session_from_body());
assert!(!cfg.is_channel());
assert!(!cfg.is_cron());
}
#[test]
fn channel_predicates() {
let cfg = PipelineConfig::channel("telegram");
assert!(cfg.is_standard_inference());
assert!(!cfg.is_streaming_inference());
assert!(cfg.enforces_authority());
assert!(cfg.can_execute_tools());
assert!(!cfg.resolves_session_from_body());
assert!(cfg.is_channel());
assert!(!cfg.is_cron());
}
#[test]
fn cron_predicates() {
let cfg = PipelineConfig::cron();
assert!(cfg.is_standard_inference());
assert!(!cfg.is_streaming_inference());
assert!(!cfg.enforces_authority());
assert!(cfg.can_execute_tools());
assert!(!cfg.resolves_session_from_body());
assert!(!cfg.is_channel());
assert!(cfg.is_cron());
}
#[test]
fn all_presets_have_injection_defense() {
assert!(PipelineConfig::api().injection_defense);
assert!(PipelineConfig::streaming().injection_defense);
assert!(PipelineConfig::channel("test").injection_defense);
assert!(PipelineConfig::cron().injection_defense);
}
#[test]
fn all_presets_have_post_turn_ingest() {
assert!(PipelineConfig::api().post_turn_ingest);
assert!(PipelineConfig::streaming().post_turn_ingest);
assert!(PipelineConfig::channel("test").post_turn_ingest);
assert!(PipelineConfig::cron().post_turn_ingest);
}
#[test]
fn standard_inference_paths_have_full_guards() {
let api = PipelineConfig::api();
let channel = PipelineConfig::channel("telegram");
let cron = PipelineConfig::cron();
for cfg in [&api, &channel, &cron] {
assert_eq!(cfg.inference_mode, InferenceMode::Standard);
assert_eq!(cfg.guard_set, GuardSetPreset::Full);
assert_eq!(cfg.cache_guard_set, GuardSetPreset::Cached);
}
}
#[test]
fn only_api_has_nickname_refinement() {
assert!(PipelineConfig::api().nickname_refinement);
assert!(!PipelineConfig::streaming().nickname_refinement);
assert!(!PipelineConfig::channel("test").nickname_refinement);
assert!(!PipelineConfig::cron().nickname_refinement);
}
#[test]
fn only_channel_has_specialist_controls() {
assert!(!PipelineConfig::api().specialist_controls);
assert!(!PipelineConfig::streaming().specialist_controls);
assert!(PipelineConfig::channel("test").specialist_controls);
assert!(!PipelineConfig::cron().specialist_controls);
}
#[test]
fn streaming_never_executes_tools() {
let cfg = PipelineConfig::streaming();
assert!(!cfg.can_execute_tools());
assert!(!cfg.shortcuts_enabled);
assert!(!cfg.decomposition_gate);
assert!(!cfg.delegated_execution);
}
#[test]
fn session_resolution_modes_are_correct() {
assert_eq!(
PipelineConfig::api().session_resolution,
SessionResolutionMode::FromBody
);
assert_eq!(
PipelineConfig::streaming().session_resolution,
SessionResolutionMode::FromBody
);
assert_eq!(
PipelineConfig::channel("discord").session_resolution,
SessionResolutionMode::FromChannel {
platform: "discord".into()
}
);
assert_eq!(
PipelineConfig::cron().session_resolution,
SessionResolutionMode::Dedicated
);
}
#[test]
fn provided_session_resolution_stores_id() {
let mode = SessionResolutionMode::Provided {
session_id: "test-session-123".into(),
};
match mode {
SessionResolutionMode::Provided { session_id } => {
assert_eq!(session_id, "test-session-123");
}
_ => panic!("expected Provided variant"),
}
}
}