use serde_json::Value;
use tandem_types::{EngineEvent, TenantContext};
use tokio::sync::broadcast::error::RecvError;
use crate::audit::append_protected_audit_event;
use crate::AppState;
pub(crate) fn data_boundary_event_needs_protected_audit(event: &EngineEvent) -> bool {
event.event_type.starts_with("data_boundary.")
&& event
.properties
.get("action")
.and_then(Value::as_str)
.is_some_and(|action| action != "allow")
}
fn tenant_context_from_event(event: &EngineEvent) -> TenantContext {
let tenant = event.properties.get("tenant");
let field = |name: &str| {
tenant
.and_then(|value| value.get(name))
.and_then(Value::as_str)
.map(str::to_string)
};
let mut context = TenantContext::local_implicit();
if let Some(org_id) = field("organization_id") {
context.org_id = org_id;
}
if let Some(workspace_id) = field("workspace_id") {
context.workspace_id = workspace_id;
}
context.deployment_id = field("deployment_id");
context
}
pub(crate) async fn record_data_boundary_protected_audit(
state: &AppState,
event: &EngineEvent,
) -> bool {
if !data_boundary_event_needs_protected_audit(event) {
return false;
}
let tenant_context = tenant_context_from_event(event);
let actor = event
.properties
.get("sessionID")
.and_then(Value::as_str)
.map(str::to_string);
append_protected_audit_event(
state,
event.event_type.clone(),
&tenant_context,
actor,
event.properties.clone(),
)
.await
.is_ok()
}
pub async fn run_data_boundary_audit_bridge(state: AppState) {
let mut rx = state.event_bus.subscribe();
loop {
match rx.recv().await {
Ok(event) => {
let _ = record_data_boundary_protected_audit(&state, &event).await;
}
Err(RecvError::Closed) => break,
Err(RecvError::Lagged(_)) => continue,
}
}
}