use crate::Event;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum TrustTag {
#[default]
Unspecified = 0,
TrustedUser = 1,
QuarantinedContent = 2,
}
impl TrustTag {
#[must_use]
pub const fn as_u8(self) -> u8 {
self as u8
}
#[must_use]
pub const fn from_u8(byte: u8) -> Option<Self> {
match byte {
0 => Some(Self::Unspecified),
1 => Some(Self::TrustedUser),
2 => Some(Self::QuarantinedContent),
_ => None,
}
}
#[must_use]
pub const fn is_quarantined(self) -> bool {
matches!(self, Self::QuarantinedContent)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Unspecified => "unspecified",
Self::TrustedUser => "trusted_user",
Self::QuarantinedContent => "quarantined_content",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct TrifectaLegs {
pub private_data_access: bool,
pub untrusted_content_in_context: bool,
pub external_comms_capability: bool,
}
impl TrifectaLegs {
#[must_use]
pub const fn is_live(self) -> bool {
self.private_data_access
&& self.untrusted_content_in_context
&& self.external_comms_capability
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct GrantedCapabilities {
pub private_data_access: bool,
pub external_comms: bool,
}
#[must_use]
pub fn trifecta_legs(events: &[Event], granted: GrantedCapabilities) -> TrifectaLegs {
TrifectaLegs {
private_data_access: granted.private_data_access,
untrusted_content_in_context: any_untrusted(events),
external_comms_capability: granted.external_comms,
}
}
#[must_use]
pub fn any_untrusted(events: &[Event]) -> bool {
events.iter().any(|event| event.trust.is_quarantined())
}
#[must_use]
pub fn any_untrusted_excluding(
events: &[(u64, Event)],
excised: &std::collections::BTreeSet<u64>,
) -> bool {
events
.iter()
.any(|(pos, event)| !excised.contains(pos) && event.trust.is_quarantined())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Event;
#[test]
fn ingress_tags_tool_output_quarantined_and_user_trusted() {
let tool_output = Event::quarantined("tool_result", b"<fetched page>".to_vec());
assert_eq!(tool_output.trust, TrustTag::QuarantinedContent);
assert!(tool_output.trust.is_quarantined());
let user_message = Event::trusted("user_msg", b"summarize my inbox".to_vec());
assert_eq!(user_message.trust, TrustTag::TrustedUser);
assert!(!user_message.trust.is_quarantined());
let marker = Event::new("turn_start", Vec::new());
assert_eq!(marker.trust, TrustTag::Unspecified);
}
#[test]
fn trifecta_is_live_only_when_all_three_legs_hold() {
let clean = [Event::trusted("user_msg", b"hi".to_vec())];
let tainted = [
Event::trusted("user_msg", b"hi".to_vec()),
Event::quarantined("output_msg", b"<tool result>".to_vec()),
];
let both = GrantedCapabilities {
private_data_access: true,
external_comms: true,
};
assert!(trifecta_legs(&tainted, both).is_live());
assert!(
!trifecta_legs(&clean, both).is_live(),
"no untrusted content in context"
);
assert!(
!trifecta_legs(
&tainted,
GrantedCapabilities {
private_data_access: true,
external_comms: false,
}
)
.is_live(),
"no external comms capability"
);
assert!(
!trifecta_legs(
&tainted,
GrantedCapabilities {
private_data_access: false,
external_comms: true,
}
)
.is_live(),
"no private data access"
);
assert!(
!trifecta_legs(&tainted, GrantedCapabilities::default()).is_live(),
"no capabilities granted"
);
assert!(trifecta_legs(&tainted, both).untrusted_content_in_context);
assert!(!trifecta_legs(&clean, both).untrusted_content_in_context);
}
#[test]
fn excluded_positions_recover_the_seed() {
use std::collections::BTreeSet;
let events: Vec<(u64, Event)> = vec![
(0, Event::trusted("user_msg", b"hi".to_vec())),
(
1,
Event::quarantined("output_msg", b"<fetched page>".to_vec()),
),
(2, Event::new("turn_complete", Vec::new())),
];
let none = BTreeSet::new();
assert!(any_untrusted_excluding(&events, &none), "taint present");
let excised: BTreeSet<u64> = [1].into();
assert!(
!any_untrusted_excluding(&events, &excised),
"excising the quarantined position re-derives the seed clean"
);
let wrong: BTreeSet<u64> = [0, 2].into();
assert!(any_untrusted_excluding(&events, &wrong));
let mut later = events;
later.push((3, Event::quarantined("output_msg", b"<new fetch>".to_vec())));
assert!(any_untrusted_excluding(&later, &excised));
}
#[test]
fn any_untrusted_detects_a_single_quarantined_event() {
let clean = [
Event::trusted("user_msg", b"hi".to_vec()),
Event::new("turn_start", Vec::new()),
];
assert!(!any_untrusted(&clean));
assert!(!any_untrusted(&[]));
let tainted = [
Event::trusted("user_msg", b"hi".to_vec()),
Event::new("turn_start", Vec::new()),
Event::quarantined("output_msg", b"<fetched page>".to_vec()),
];
assert!(any_untrusted(&tainted));
}
}