use lonca_core::oplog::{FORMAT_CURRENT, SignedOp};
use lonca_core::ops::{Actor, Decision};
use serde::Serialize;
use crate::config::{CedarOutcome, PolicyBundle};
use crate::decide::{Ctx, decide, session_context};
use crate::state::State;
pub const WAYS_OUT: [&str; 8] = [
"policy.update",
"unfreeze",
"approve",
"reject",
"human.rotate",
"human.recover",
"daemon.rotate",
"daemon.recover",
];
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Difference {
pub seq: u64,
pub time_ms: i64,
pub op: String,
pub actor: String,
pub recorded: Decision,
pub candidate: Decision,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
pub struct Lockout {
pub human: String,
pub action: String,
pub reasons: Vec<String>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
pub struct Comparison {
pub entries: u64,
pub differences: Vec<Difference>,
pub lockouts: Vec<Lockout>,
}
pub fn compare(ops: &[SignedOp], candidate: &PolicyBundle) -> Comparison {
let mut state = State::new();
let mut out = Comparison::default();
for op in ops {
let e = &op.entry;
let d = decide(
&state,
&e.actor,
&e.op,
e.human.as_ref(),
&Ctx {
format: e.format,
time_ms: e.time_ms,
policy: candidate,
facts: e.facts.as_ref(),
evidence: &e.evidence,
symbols_engine: e.symbols_engine,
},
);
if d != e.decision {
out.differences.push(Difference {
seq: e.seq,
time_ms: e.time_ms,
op: e.op.kind().as_str().into(),
actor: e.actor.label(),
recorded: e.decision.clone(),
candidate: d,
});
}
out.entries += 1;
state.apply(op);
}
out.lockouts = lockouts(&state, candidate);
out
}
pub fn lockouts(state: &State, candidate: &PolicyBundle) -> Vec<Lockout> {
let mut out = Vec::new();
for name in state.humans.keys() {
let human = Actor::Human { name: name.clone() };
for action in WAYS_OUT {
let context = session_context(state, &human, FORMAT_CURRENT);
if let CedarOutcome::Deny(reasons) = candidate.evaluate(&human, action, context) {
out.push(Lockout {
human: name.clone(),
action: action.into(),
reasons,
});
}
}
}
out
}