use serde_json::json;
use uuid::Uuid;
use crate::logging::{AuditSink, FactsEmitter, StageLogger};
use crate::types::{Action, ApplyReport, Plan};
use crate::api::errors::{exit_code_for, ErrorId};
use log::Level;
pub(crate) fn enforce<E: FactsEmitter, A: AuditSink>(
api: &super::super::Switchyard<E, A>,
plan: &Plan,
pid: Uuid,
dry: bool,
t0: std::time::Instant,
slog: &StageLogger<'_>,
) -> Option<ApplyReport> {
if api.policy.apply.override_preflight || dry {
return None;
}
let gating_errors =
crate::policy::gating::gating_errors(&api.policy, api.owner.as_deref(), plan);
if gating_errors.is_empty() {
return None;
}
api.audit
.log(Level::Warn, "apply: policy gating rejected plan (E_POLICY)");
let ec = exit_code_for(ErrorId::E_POLICY);
for (idx, act) in plan.actions.iter().enumerate() {
let aid = crate::types::ids::action_id(&pid, act, idx).to_string();
let path = match act {
Action::EnsureSymlink { target, .. } | Action::RestoreFromBackup { target } => {
target.as_path().display().to_string()
}
};
slog.apply_result()
.merge(&json!({
"action_id": aid,
"path": path,
"error_id": "E_POLICY",
"exit_code": ec,
}))
.emit_failure();
}
slog.apply_result()
.merge(&json!({
"error_id": "E_POLICY",
"exit_code": ec,
"perf": {"hash_ms": 0u64, "backup_ms": 0u64, "swap_ms": 0u64},
}))
.emit_failure();
let duration_ms = u64::try_from(t0.elapsed().as_millis()).unwrap_or(u64::MAX);
Some(ApplyReport {
executed: Vec::new(),
duration_ms,
errors: gating_errors,
plan_uuid: Some(pid),
rolled_back: false,
rollback_errors: Vec::new(),
})
}