use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Trigger {
pub source: TriggerSource,
pub source_kind: SourceKind,
pub source_label: String,
pub event_label: String,
pub payload_visibility: PayloadVisibility,
pub payload_summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload: Option<serde_json::Value>,
pub idempotency_key: String,
pub replacement_policy: ReplacementPolicy,
pub trace_id: String,
pub authority: TriggerAuthority,
pub received_at: DateTime<Utc>,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum TriggerSource {
Mcp { server_name: String, method: String },
Local { subkind: String },
AgentDelegate {
agent_id: String,
delegation_id: String,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SourceKind {
Local,
Mcp,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum PayloadVisibility {
Local,
Shared,
Redacted,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TriggerAuthority {
pub principal_id: String,
pub principal_label: String,
pub credential_scope: CredentialScope,
#[serde(default)]
pub allowed_source_actions: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub expires_at: Option<DateTime<Utc>>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReplacementPolicy {
LatestReplaces,
Coalesce,
Drop,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub enum CredentialScope {
User,
Project,
Team,
Agent,
None,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TriggerState {
Received,
Accepted,
Deduped,
CycleSuppressed,
PermissionDenied,
NeedsApproval,
Running,
Failed,
Completed,
}
impl TriggerState {
pub fn is_terminal(self) -> bool {
matches!(
self,
Self::Deduped
| Self::CycleSuppressed
| Self::PermissionDenied
| Self::NeedsApproval
| Self::Failed
| Self::Completed
)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct TriggerRecord {
pub schema_version: u32,
pub source: TriggerSource,
pub source_kind: SourceKind,
pub source_label: String,
pub event_label: String,
pub trace_id: String,
pub authority: TriggerAuthority,
pub idempotency_key: String,
pub replacement_policy: ReplacementPolicy,
pub received_at: DateTime<Utc>,
pub state: TriggerState,
pub payload_visibility: PayloadVisibility,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub payload_summary: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evaluator_decision: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub result_link: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rule_name: Option<String>,
}
impl TriggerRecord {
pub const SCHEMA_VERSION: u32 = 1;
pub fn received_from(trigger: &Trigger) -> Self {
Self {
schema_version: Self::SCHEMA_VERSION,
source: trigger.source.clone(),
source_kind: trigger.source_kind,
source_label: trigger.source_label.clone(),
event_label: trigger.event_label.clone(),
trace_id: trigger.trace_id.clone(),
authority: trigger.authority.clone(),
idempotency_key: trigger.idempotency_key.clone(),
replacement_policy: trigger.replacement_policy,
received_at: trigger.received_at,
state: TriggerState::Received,
payload_visibility: trigger.payload_visibility,
payload_summary: trigger.payload_summary.clone(),
evaluator_decision: None,
result_link: None,
rule_name: None,
}
}
pub const CUSTOM_TYPE: &'static str = "trigger";
}
#[cfg(test)]
tests_bridge_macro::tests_bridge!("trigger_engine/types");