use super::emitters::DecisionEmitter;
use super::event_types::{reason_codes, DecisionEvent, PolicyDecisionEventContext};
use super::normalization::refresh_fulfillment_normalization;
use serde_json::Value;
use std::sync::Arc;
pub struct DecisionEmitterGuard {
emitter: Arc<dyn DecisionEmitter>,
event: Option<DecisionEvent>,
}
impl DecisionEmitterGuard {
pub fn new(
emitter: Arc<dyn DecisionEmitter>,
source: String,
tool_call_id: String,
tool: String,
) -> Self {
Self {
emitter,
event: Some(DecisionEvent::new(source, tool_call_id, tool)),
}
}
pub fn set_request_id(&mut self, id: Option<Value>) {
if let Some(ref mut event) = self.event {
event.data.request_id = id;
}
}
pub fn set_mandate_info(
&mut self,
mandate_id: Option<String>,
use_id: Option<String>,
use_count: Option<u32>,
) {
if let Some(ref mut event) = self.event {
event.data.mandate_id = mandate_id;
event.data.use_id = use_id;
event.data.use_count = use_count;
}
}
pub fn set_mandate_matches(
&mut self,
scope_match: Option<bool>,
kind_match: Option<bool>,
tx_ref_match: Option<bool>,
) {
if let Some(ref mut event) = self.event {
event.data.mandate_scope_match = scope_match;
event.data.mandate_kind_match = kind_match;
event.data.transaction_ref_match = tx_ref_match;
}
}
pub fn set_latencies(&mut self, authz_ms: Option<u64>, store_ms: Option<u64>) {
if let Some(ref mut event) = self.event {
event.data.authz_latency_ms = authz_ms;
event.data.store_latency_ms = store_ms;
}
}
pub fn set_tool_match(
&mut self,
tool_classes: Vec<String>,
matched_tool_classes: Vec<String>,
match_basis: Option<String>,
matched_rule: Option<String>,
) {
if let Some(ref mut event) = self.event {
event.data.tool_classes = tool_classes;
event.data.matched_tool_classes = matched_tool_classes;
event.data.match_basis = match_basis;
event.data.matched_rule = matched_rule;
}
}
pub fn set_policy_context(&mut self, context: PolicyDecisionEventContext) {
if let Some(ref mut event) = self.event {
let PolicyDecisionEventContext {
typed_decision,
policy_version,
policy_digest,
obligations,
obligation_outcomes,
approval_state,
approval_artifact,
approval_freshness,
approval_failure_reason,
scope_contract,
scope_evaluation_state,
scope_failure_reason,
restrict_scope_present,
restrict_scope_target,
restrict_scope_match,
restrict_scope_reason,
redaction_contract,
redaction_applied_state,
redaction_reason,
redaction_failure_reason,
redact_args_present,
redact_args_target,
redact_args_mode,
redact_args_result,
redact_args_reason,
fail_closed,
lane,
principal,
auth_context_summary,
auth_scheme,
auth_issuer,
delegated_from,
delegation_depth,
tool_definition_binding,
} = context;
event.data.typed_decision = typed_decision;
event.data.policy_version = policy_version;
event.data.policy_digest = policy_digest;
event.data.apply_policy_snapshot_projection();
event
.data
.apply_tool_definition_binding(tool_definition_binding.as_ref());
event.data.obligations = obligations;
event.data.obligation_outcomes = obligation_outcomes;
event.data.approval_state = approval_state;
if let Some(artifact) = approval_artifact {
event.data.approval_id = Some(artifact.approval_id);
event.data.approver = Some(artifact.approver);
event.data.issued_at = Some(artifact.issued_at);
event.data.expires_at = Some(artifact.expires_at);
event.data.scope = Some(artifact.scope);
event.data.approval_bound_tool = Some(artifact.bound_tool);
event.data.approval_bound_resource = Some(artifact.bound_resource);
}
event.data.approval_freshness = approval_freshness;
event.data.approval_failure_reason = approval_failure_reason;
if let Some(contract) = scope_contract {
event.data.scope_type = Some(contract.scope_type);
event.data.scope_value = Some(contract.scope_value);
event.data.scope_match_mode = Some(contract.scope_match_mode);
}
event.data.scope_evaluation_state = scope_evaluation_state;
event.data.scope_failure_reason = scope_failure_reason;
event.data.restrict_scope_present = restrict_scope_present;
event.data.restrict_scope_target = restrict_scope_target;
event.data.restrict_scope_match = restrict_scope_match;
event.data.restrict_scope_reason = restrict_scope_reason;
if let Some(contract) = redaction_contract {
event.data.redaction_target = Some(contract.redaction_target);
event.data.redaction_mode = Some(contract.redaction_mode);
event.data.redaction_scope = Some(contract.redaction_scope);
}
event.data.redaction_applied_state = redaction_applied_state;
event.data.redaction_reason = redaction_reason;
event.data.redaction_failure_reason = redaction_failure_reason;
event.data.redact_args_present = redact_args_present;
event.data.redact_args_target = redact_args_target;
event.data.redact_args_mode = redact_args_mode;
event.data.redact_args_result = redact_args_result;
event.data.redact_args_reason = redact_args_reason;
event.data.fail_closed = fail_closed;
event.data.lane = lane;
event.data.principal = principal;
event.data.auth_context_summary = auth_context_summary;
event.data.auth_scheme = auth_scheme;
event.data.auth_issuer = auth_issuer;
event.data.delegated_from = delegated_from;
event.data.delegation_depth = delegation_depth;
refresh_fulfillment_normalization(&mut event.data);
}
}
pub fn emit_allow(mut self, reason_code: &str) {
if let Some(event) = self.event.take() {
self.emitter.emit(&event.allow(reason_code));
}
}
pub fn emit_deny(mut self, reason_code: &str, reason: Option<String>) {
if let Some(event) = self.event.take() {
self.emitter.emit(&event.deny(reason_code, reason));
}
}
pub fn emit_error(mut self, reason_code: &str, reason: Option<String>) {
if let Some(event) = self.event.take() {
self.emitter.emit(&event.error(reason_code, reason));
}
}
pub fn emit_event(mut self, event: DecisionEvent) {
self.event = None;
self.emitter.emit(&event);
}
}
impl Drop for DecisionEmitterGuard {
fn drop(&mut self) {
if let Some(event) = self.event.take() {
self.emitter.emit(&event.error(
reason_codes::S_INTERNAL_ERROR,
Some(
"Decision guard dropped without explicit emit (possible panic or early return)"
.to_string(),
),
));
}
}
}