holon 0.14.1

A headless, event-driven runtime for long-lived agents
Documentation
use crate::types::{MessageKind, MessageOrigin, TrustLevel};

#[derive(Debug, Clone)]
pub struct PolicyDecision {
    pub allowed: bool,
    pub reason: String,
}

impl PolicyDecision {
    pub fn allow(reason: impl Into<String>) -> Self {
        Self {
            allowed: true,
            reason: reason.into(),
        }
    }

    pub fn deny(reason: impl Into<String>) -> Self {
        Self {
            allowed: false,
            reason: reason.into(),
        }
    }
}

pub fn validate_message_kind_for_origin(
    kind: &MessageKind,
    origin: &MessageOrigin,
) -> PolicyDecision {
    match (kind, origin) {
        (MessageKind::OperatorPrompt, MessageOrigin::Operator { .. }) => {
            PolicyDecision::allow("operator prompt allowed")
        }
        (MessageKind::WebhookEvent, MessageOrigin::Webhook { .. }) => {
            PolicyDecision::allow("webhook event allowed")
        }
        (MessageKind::CallbackEvent, MessageOrigin::Callback { .. }) => {
            PolicyDecision::allow("callback event allowed")
        }
        (MessageKind::ChannelEvent, MessageOrigin::Channel { .. }) => {
            PolicyDecision::allow("channel event allowed")
        }
        (MessageKind::TimerTick, MessageOrigin::Timer { .. }) => {
            PolicyDecision::allow("timer tick allowed")
        }
        (MessageKind::SystemTick | MessageKind::InternalFollowup, MessageOrigin::System { .. }) => {
            PolicyDecision::allow("system event allowed")
        }
        (MessageKind::TaskStatus | MessageKind::TaskResult, MessageOrigin::Task { .. }) => {
            PolicyDecision::allow("task event allowed")
        }
        (MessageKind::Control, MessageOrigin::Operator { .. })
        | (MessageKind::Control, MessageOrigin::System { .. }) => {
            PolicyDecision::allow("control event allowed")
        }
        _ => PolicyDecision::deny("message kind does not match origin"),
    }
}

pub fn default_trust_for_origin(origin: &MessageOrigin) -> TrustLevel {
    match origin {
        MessageOrigin::Operator { .. } => TrustLevel::TrustedOperator,
        MessageOrigin::System { .. } | MessageOrigin::Task { .. } | MessageOrigin::Timer { .. } => {
            TrustLevel::TrustedSystem
        }
        MessageOrigin::Webhook { .. } => TrustLevel::TrustedIntegration,
        MessageOrigin::Callback { .. } => TrustLevel::TrustedIntegration,
        MessageOrigin::Channel { .. } => TrustLevel::UntrustedExternal,
    }
}

#[cfg(test)]
mod tests {
    use crate::types::MessageOrigin;

    use super::*;

    #[test]
    fn mismatched_origin_is_denied() {
        let decision = validate_message_kind_for_origin(
            &MessageKind::WebhookEvent,
            &MessageOrigin::Operator { actor_id: None },
        );
        assert!(!decision.allowed);
    }
}