use std::sync::atomic::{AtomicBool, Ordering};
use super::types::{CaptureDecision, CaptureResult, ObservabilityStore};
static ENABLED: AtomicBool = AtomicBool::new(false);
pub fn set_enabled(enabled: bool) {
ENABLED.store(enabled, Ordering::Release);
}
pub fn set_payload_capture_enabled(enabled: bool) {
set_enabled(enabled);
}
#[must_use]
pub fn is_enabled() -> bool {
ENABLED.load(Ordering::Acquire)
}
#[must_use]
pub fn is_payload_capture_enabled() -> bool {
is_enabled()
}
pub(crate) fn gate(store: &dyn ObservabilityStore, result: CaptureResult) -> CaptureResult {
if is_enabled() && store.acknowledge_pii_redaction() {
return result;
}
CaptureResult {
system_instructions: downgrade(result.system_instructions),
input_messages: downgrade(result.input_messages),
output_messages: downgrade(result.output_messages),
}
}
fn downgrade(decision: CaptureDecision) -> CaptureDecision {
match decision {
CaptureDecision::Reference(r) => CaptureDecision::Reference(r),
CaptureDecision::Inline | CaptureDecision::Omit => CaptureDecision::Omit,
}
}
#[cfg(test)]
mod tests {
use super::super::types::{CaptureDecision, CaptureResult, ObservabilityStore, PayloadBundle};
use super::*;
use async_trait::async_trait;
use std::sync::Mutex;
static GATE_LOCK: Mutex<()> = Mutex::new(());
fn lock_gate() -> std::sync::MutexGuard<'static, ()> {
match GATE_LOCK.lock() {
Ok(g) => g,
Err(p) => p.into_inner(),
}
}
struct GateGuard {
previous: bool,
}
impl GateGuard {
fn enable() -> Self {
let previous = is_enabled();
set_enabled(true);
Self { previous }
}
fn disable() -> Self {
let previous = is_enabled();
set_enabled(false);
Self { previous }
}
}
impl Drop for GateGuard {
fn drop(&mut self) {
set_enabled(self.previous);
}
}
struct DefaultStore;
#[async_trait]
impl ObservabilityStore for DefaultStore {
async fn capture(&self, _bundle: &PayloadBundle) -> anyhow::Result<CaptureResult> {
Ok(CaptureResult {
system_instructions: CaptureDecision::Inline,
input_messages: CaptureDecision::Inline,
output_messages: CaptureDecision::Inline,
})
}
}
struct AttestingStore;
#[async_trait]
impl ObservabilityStore for AttestingStore {
async fn capture(&self, _bundle: &PayloadBundle) -> anyhow::Result<CaptureResult> {
Ok(CaptureResult {
system_instructions: CaptureDecision::Inline,
input_messages: CaptureDecision::Inline,
output_messages: CaptureDecision::Inline,
})
}
fn acknowledge_pii_redaction(&self) -> bool {
true
}
}
fn inline_result() -> CaptureResult {
CaptureResult {
system_instructions: CaptureDecision::Inline,
input_messages: CaptureDecision::Inline,
output_messages: CaptureDecision::Inline,
}
}
fn assert_all_omit(result: &CaptureResult) {
assert!(matches!(result.system_instructions, CaptureDecision::Omit));
assert!(matches!(result.input_messages, CaptureDecision::Omit));
assert!(matches!(result.output_messages, CaptureDecision::Omit));
}
fn assert_all_inline(result: &CaptureResult) {
assert!(matches!(
result.system_instructions,
CaptureDecision::Inline
));
assert!(matches!(result.input_messages, CaptureDecision::Inline));
assert!(matches!(result.output_messages, CaptureDecision::Inline));
}
#[test]
fn default_store_with_gate_open_still_omits_inline() {
let _g = lock_gate();
let _gate = GateGuard::enable();
let result = gate(&DefaultStore, inline_result());
assert_all_omit(&result);
}
#[test]
fn default_store_with_gate_closed_omits_inline() {
let _g = lock_gate();
let _gate = GateGuard::disable();
let result = gate(&DefaultStore, inline_result());
assert_all_omit(&result);
}
#[test]
fn attesting_store_with_gate_closed_still_omits_inline() {
let _g = lock_gate();
let _gate = GateGuard::disable();
let result = gate(&AttestingStore, inline_result());
assert_all_omit(&result);
}
#[test]
fn attesting_store_with_gate_open_passes_inline_through() {
let _g = lock_gate();
let _gate = GateGuard::enable();
let result = gate(&AttestingStore, inline_result());
assert_all_inline(&result);
}
#[test]
fn references_pass_through_when_gate_is_closed() {
let _g = lock_gate();
let _gate = GateGuard::disable();
let input = CaptureResult {
system_instructions: CaptureDecision::Reference("sys-1".into()),
input_messages: CaptureDecision::Reference("in-1".into()),
output_messages: CaptureDecision::Omit,
};
let result = gate(&DefaultStore, input);
assert!(matches!(
result.system_instructions,
CaptureDecision::Reference(ref r) if r == "sys-1"
));
assert!(matches!(
result.input_messages,
CaptureDecision::Reference(ref r) if r == "in-1"
));
assert!(matches!(result.output_messages, CaptureDecision::Omit));
}
}