use std::collections::{BTreeMap, BTreeSet};
use super::effect::catalogue::Origin;
use super::types::{
Anomaly, AskPolicy, Classification, Contribution, Decision, EvalContext, Event,
OptimizeCandidate, OptimizeContext, ReportedEffect, Rewrite,
};
use crate::generated::types::Verdict;
pub fn rank(verdict: &Verdict) -> u8 {
match verdict {
Verdict::Allow => 0,
Verdict::Optimize => 1,
Verdict::Ask => 2,
Verdict::Block => 3,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AskDelivery {
Agent,
Fallback(AskFallback),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AskFallback {
Allow,
Block,
}
impl AskPolicy {
pub fn delivery(&self, can_ask: bool) -> AskDelivery {
let prompts = self.mode != "platform" && can_ask;
if prompts {
AskDelivery::Agent
} else if self.fallback == "block" {
AskDelivery::Fallback(AskFallback::Block)
} else {
AskDelivery::Fallback(AskFallback::Allow)
}
}
}
pub fn event_can_ask(event: &Event) -> bool {
event
.expressible
.as_ref()
.is_some_and(|set| set.iter().any(|verdict| verdict == "ask"))
}
fn ask_strictness(contribution: &Contribution, can_ask: bool) -> u8 {
if contribution.verdict != Verdict::Ask {
return 1;
}
if contribution.hold.is_some() {
return 0;
}
match contribution
.ask
.as_ref()
.map(|policy| policy.delivery(can_ask))
{
Some(AskDelivery::Fallback(AskFallback::Block)) => 0,
Some(AskDelivery::Fallback(AskFallback::Allow)) => 2,
Some(AskDelivery::Agent) | None => 1,
}
}
pub fn join(contributions: &[Contribution], can_ask: bool) -> Option<&Contribution> {
best(contributions.iter(), can_ask)
}
fn best<'a>(
contributions: impl Iterator<Item = &'a Contribution>,
can_ask: bool,
) -> Option<&'a Contribution> {
contributions.min_by(|left, right| {
rank(&right.verdict)
.cmp(&rank(&left.verdict))
.then_with(|| ask_strictness(left, can_ask).cmp(&ask_strictness(right, can_ask)))
.then_with(|| artifact_id(left).cmp(artifact_id(right)))
})
}
pub fn assemble(contributions: &[Contribution], ctx: &EvalContext<'_>) -> Decision {
let active = without_same_layer_conflicts(contributions);
let enforcing: Vec<&Contribution> = active
.iter()
.copied()
.filter(|c| c.is_enforcing())
.collect();
let monitoring: Vec<&Contribution> = active
.iter()
.copied()
.filter(|c| !c.is_enforcing())
.collect();
let can_ask = event_can_ask(ctx.event);
let joined = best(enforcing.iter().copied(), can_ask);
let shadow = best(monitoring.iter().copied(), can_ask);
let (actual_optimize, mut optimize_shadowed) =
if joined.is_some_and(|c| c.verdict == Verdict::Optimize) {
select_optimize(&enforcing)
} else {
(None, Vec::new())
};
let (monitor_optimize, monitor_shadowed) =
if shadow.is_some_and(|c| c.verdict == Verdict::Optimize) {
select_optimize(&monitoring)
} else {
(None, Vec::new())
};
optimize_shadowed.extend(monitor_shadowed);
optimize_shadowed.truncate(32);
let decided = actual_optimize.or(joined);
let record_source = match decided {
Some(candidate) if matches!(candidate.verdict, Verdict::Block | Verdict::Ask) => {
Some(candidate)
}
Some(candidate) if candidate.verdict == Verdict::Allow && monitor_optimize.is_some() => {
monitor_optimize
}
Some(candidate) => Some(candidate),
None => shadow
.filter(|candidate| matches!(candidate.verdict, Verdict::Block | Verdict::Ask))
.or(monitor_optimize),
};
let inconclusive_facts: Vec<String> = active
.iter()
.flat_map(|c| c.inconclusive.iter().cloned())
.collect::<BTreeSet<String>>()
.into_iter()
.collect();
let anomalies: Vec<Anomaly> = active
.iter()
.flat_map(|c| {
c.anomalies.iter().map(|code| Anomaly {
code: code.clone(),
artifact_id: c.artifact_id.clone(),
atom_id: c.atom_id.clone(),
})
})
.collect();
Decision {
verdict: decided.map(|c| c.verdict).unwrap_or(Verdict::Allow),
artifact_id: record_source.and_then(|c| c.artifact_id.clone()),
atom_id: record_source.and_then(|c| c.atom_id.clone()),
policy_public_id: record_source.and_then(|c| c.policy_public_id.clone()),
dimension: record_source.and_then(|c| c.dimension.clone()),
mode: record_source.map(|c| c.mode.clone()),
tier: record_source.and_then(|c| c.tier),
reason: record_source.map(|c| c.reason.clone()).unwrap_or_default(),
would_have_verdict: shadow.map(|c| c.verdict),
inconclusive_facts,
rewrite: rewrite(enforcing.iter().copied()),
optimize: (actual_optimize.is_some() || monitor_optimize.is_some()).then(|| {
OptimizeContext {
actual: actual_optimize.and_then(selected_evidence),
monitor: monitor_optimize.and_then(selected_evidence),
shadowed: optimize_shadowed,
}
}),
hold: decided.and_then(|c| c.hold.clone()),
effects: ctx.classification.effects.clone(),
effect_report: effect_report(ctx.classification),
undecided: active.is_empty(),
unknown: ctx.classification.unknown.clone(),
anomalies,
ground_key: decided.and_then(|c| c.exception_ground_key.clone()),
warnings: ctx.warnings.clone(),
}
}
fn effect_report(classification: &Classification) -> Vec<ReportedEffect> {
classification
.tuples_with_origins()
.into_iter()
.map(|(effect, origins)| {
let target = if effect.target_class.0 == "network_host" {
classification.urls.first().map(|url| url.host.clone())
} else {
classification
.paths
.iter()
.find(|path| path.class == effect.target_class)
.map(|path| path.value.clone())
};
ReportedEffect {
origin: origins.iter().map(Origin::label).collect(),
target,
effect,
}
})
.collect()
}
fn without_same_layer_conflicts(contributions: &[Contribution]) -> Vec<&Contribution> {
type GroupKey = (Vec<String>, String);
let mut groups: BTreeMap<GroupKey, Vec<&super::types::OptimizeCandidateInternal>> =
BTreeMap::new();
for contribution in contributions {
let Some(optimize) = contribution.optimize.as_ref() else {
continue;
};
groups
.entry((optimize.layer_path.clone(), contribution.mode.0.clone()))
.or_default()
.push(optimize);
}
let conflicted: BTreeSet<GroupKey> = groups
.into_iter()
.filter_map(|(key, candidates)| {
let first = candidates.first()?;
candidates
.iter()
.skip(1)
.any(|candidate| {
candidate.evidence.lever != first.evidence.lever
|| candidate.params.canonical_value() != first.params.canonical_value()
})
.then_some(key)
})
.collect();
contributions
.iter()
.filter(|contribution| {
contribution.optimize.as_ref().is_none_or(|optimize| {
!conflicted.contains(&(optimize.layer_path.clone(), contribution.mode.0.clone()))
})
})
.collect()
}
fn select_optimize<'a>(
contributions: &[&'a Contribution],
) -> (Option<&'a Contribution>, Vec<OptimizeCandidate>) {
let mut candidates: Vec<&Contribution> = contributions
.iter()
.copied()
.filter(|contribution| {
contribution.verdict == Verdict::Optimize && contribution.optimize.is_some()
})
.collect();
candidates.sort_by(|left, right| {
candidate_path(left)
.len()
.cmp(&candidate_path(right).len())
.then_with(|| candidate_path(left).cmp(candidate_path(right)))
.then_with(|| artifact_id(left).cmp(artifact_id(right)))
});
let Some(selected) = candidates.first().copied() else {
return (None, Vec::new());
};
let selected_path = candidate_path(selected).to_vec();
let shadowed = candidates
.into_iter()
.skip(1)
.filter_map(|candidate| {
let optimize = candidate.optimize.as_ref()?;
let mut evidence = optimize.evidence.clone();
evidence.reason = if optimize.layer_path == selected_path {
"same_layer_equivalent"
} else {
"parent_overlap"
}
.to_string();
Some(evidence)
})
.collect();
(Some(selected), shadowed)
}
fn selected_evidence(contribution: &Contribution) -> Option<OptimizeCandidate> {
contribution
.optimize
.as_ref()
.map(|optimize| optimize.evidence.clone())
}
fn candidate_path(contribution: &Contribution) -> &[String] {
contribution
.optimize
.as_ref()
.map(|candidate| candidate.layer_path.as_slice())
.unwrap_or_default()
}
fn rewrite<'a>(enforcing: impl Iterator<Item = &'a Contribution>) -> Option<Rewrite> {
enforcing
.filter(|c| c.verdict == Verdict::Optimize && c.optimize.is_none())
.find_map(|c| {
c.lever.clone().map(|lever| Rewrite {
lever,
artifact_id: c.artifact_id.clone(),
steer_instruction: c.steer_instruction.clone(),
})
})
}
fn artifact_id(contribution: &Contribution) -> &str {
contribution.artifact_id.as_deref().unwrap_or("")
}
#[cfg(test)]
mod tests {
use super::*;
use crate::generated::types::{Lever, PolicyMode};
use crate::zone_eval::facts::FactSet;
use crate::zone_eval::types::{Classification, Event, MODE_ENFORCE, MODE_MONITOR};
const NOW: i64 = 1_756_742_400_000;
fn contribution(artifact_id: &str, mode: &str, verdict: Verdict) -> Contribution {
Contribution {
artifact_id: Some(artifact_id.to_string()),
atom_id: Some(format!("atom-{artifact_id}")),
policy_public_id: None,
dimension: None,
mode: PolicyMode(mode.to_string()),
tier: Some(1),
verdict,
reason: artifact_id.to_string(),
inconclusive: Vec::new(),
anomalies: Vec::new(),
hold: None,
exception_ground_key: None,
lever: None,
steer_instruction: None,
optimize: None,
ask: None,
}
}
fn decision(contributions: &[Contribution]) -> Decision {
let event = Event::default();
let classification = Classification::default();
let facts = FactSet::default();
let ctx = EvalContext::new(&event, &classification, &facts, NOW);
assemble(contributions, &ctx)
}
fn ask(artifact_id: &str, mode: &str, fallback: &str) -> Contribution {
let mut c = contribution(artifact_id, MODE_ENFORCE, Verdict::Ask);
c.ask = Some(AskPolicy {
mode: mode.to_string(),
fallback: fallback.to_string(),
});
c
}
fn decision_with(asks: &[Contribution], can_ask: bool) -> Decision {
let event = Event {
expressible: Some(if can_ask {
vec!["allow".into(), "ask".into(), "deny".into()]
} else {
vec!["allow".into(), "deny".into()]
}),
..Event::default()
};
let classification = Classification::default();
let facts = FactSet::default();
let ctx = EvalContext::new(&event, &classification, &facts, NOW);
assemble(asks, &ctx)
}
fn decided_by(asks: &[Contribution], can_ask: bool) -> Option<String> {
let event = Event {
expressible: Some(if can_ask {
vec!["allow".into(), "ask".into(), "deny".into()]
} else {
vec!["allow".into(), "deny".into()]
}),
..Event::default()
};
let classification = Classification::default();
let facts = FactSet::default();
let ctx = EvalContext::new(&event, &classification, &facts, NOW);
assemble(asks, &ctx).artifact_id
}
#[test]
fn several_asks_are_ranked_by_how_each_is_delivered() {
let asks = [
ask("a", "agent", "allow"),
ask("b", "platform", "allow"),
ask("z", "platform", "block"),
];
assert_eq!(decided_by(&asks, true).as_deref(), Some("z"));
let mut reversed = asks.clone();
reversed.reverse();
assert_eq!(decided_by(&reversed, true).as_deref(), Some("z"));
let asks = [ask("a", "platform", "allow"), ask("b", "agent", "allow")];
assert_eq!(decided_by(&asks, true).as_deref(), Some("b"));
assert_eq!(decided_by(&asks, false).as_deref(), Some("a"));
let asks = [ask("a", "platform", "allow"), ask("b", "agent", "block")];
assert_eq!(decided_by(&asks, false).as_deref(), Some("b"));
assert_eq!(decided_by(&asks, true).as_deref(), Some("b"));
let both = [ask("a", "both", "allow"), ask("b", "platform", "block")];
assert_eq!(decided_by(&both, true).as_deref(), Some("b"));
let tie = [ask("b", "both", "block"), ask("a", "platform", "block")];
assert_eq!(decided_by(&tie, true).as_deref(), Some("a"));
}
#[test]
fn a_fired_hold_is_never_outranked_by_a_weaker_ask() {
let mut held = ask("z", "platform", "allow");
held.hold = Some(crate::zone_eval::types::HoldRequest {
resolve: Some("ask_human".to_string()),
directive_template_id: None,
max_attempts: None,
timeout_s: None,
on_timeout: None,
verdict_on_approve: None,
verdict_on_reject: None,
});
let asks = [ask("a", "agent", "allow"), held];
let decision = decision_with(&asks, true);
assert_eq!(decision.artifact_id.as_deref(), Some("z"));
assert!(decision.hold.is_some());
}
#[test]
fn strictness_never_outranks_the_lattice_and_legacy_asks_are_unchanged() {
let legacy = contribution("a", MODE_ENFORCE, Verdict::Ask);
let asks = [
legacy.clone(),
contribution("b", MODE_ENFORCE, Verdict::Ask),
];
assert_eq!(decided_by(&asks, true).as_deref(), Some("a"));
let blocked = [
ask("a", "platform", "block"),
contribution("z", MODE_ENFORCE, Verdict::Block),
];
assert_eq!(
decided_by(&blocked, true).as_deref(),
Some("z"),
"a Block still decides over any ASK"
);
}
#[test]
fn an_undeclared_event_is_not_assumed_to_prompt() {
assert!(!event_can_ask(&Event::default()));
}
#[test]
fn the_effect_report_names_origins_and_targets() {
use crate::generated::types::{EffectVerb, TargetClass};
use crate::zone_eval::types::{ClassifiedPath, ClassifiedUrl, Effect, UrlBoundary};
let classification = Classification {
effects: vec![
Effect {
verb: EffectVerb("read".into()),
target_class: TargetClass("secret_material".into()),
attrs: Default::default(),
},
Effect {
verb: EffectVerb("network_egress".into()),
target_class: TargetClass("network_host".into()),
attrs: Default::default(),
},
Effect {
verb: EffectVerb("execute".into()),
target_class: TargetClass("shell".into()),
attrs: Default::default(),
},
],
paths: vec![ClassifiedPath {
class: TargetClass("secret_material".into()),
value: "~/.pgpass".into(),
}],
urls: vec![ClassifiedUrl {
value: "https://example.org/x".into(),
host: "example.org".into(),
tld: "org".into(),
scheme: "https".into(),
boundary: UrlBoundary::External,
}],
..Classification::default()
};
let report = effect_report(&classification);
let targets: Vec<Option<&str>> = report.iter().map(|r| r.target.as_deref()).collect();
assert_eq!(targets, [Some("~/.pgpass"), Some("example.org"), None]);
assert!(
report.iter().all(|r| r.origin.is_empty()),
"code rows name no entry"
);
}
#[test]
fn the_lattice_is_block_over_ask_over_optimize_over_allow() {
assert!(rank(&Verdict::Block) > rank(&Verdict::Ask));
assert!(rank(&Verdict::Ask) > rank(&Verdict::Optimize));
assert!(rank(&Verdict::Optimize) > rank(&Verdict::Allow));
}
#[test]
fn monitor_never_joins() {
let decision = decision(&[
contribution("m", MODE_MONITOR, Verdict::Block),
contribution("e", MODE_ENFORCE, Verdict::Allow),
]);
assert_eq!(decision.verdict, Verdict::Allow);
assert_eq!(decision.artifact_id.as_deref(), Some("e"));
assert_eq!(decision.would_have_verdict, Some(Verdict::Block));
assert!(
!decision.undecided,
"it was not silent, it just did not decide"
);
}
#[test]
fn an_all_monitor_ask_or_block_keeps_its_shadow_record_source() {
for verdict in [Verdict::Ask, Verdict::Block] {
let decision = decision(&[contribution("m", MODE_MONITOR, verdict)]);
assert_eq!(decision.verdict, Verdict::Allow);
assert_eq!(decision.artifact_id.as_deref(), Some("m"));
assert_eq!(decision.atom_id.as_deref(), Some("atom-m"));
assert_eq!(
decision.mode.as_ref().map(|mode| mode.0.as_str()),
Some(MODE_MONITOR)
);
assert_eq!(decision.tier, Some(1));
assert_eq!(decision.reason, "m");
assert_eq!(decision.would_have_verdict, Some(verdict));
assert!(!decision.undecided);
}
}
#[test]
fn an_all_monitor_allow_still_names_nobody() {
let decision = decision(&[contribution("m", MODE_MONITOR, Verdict::Allow)]);
assert_eq!(decision.verdict, Verdict::Allow);
assert_eq!(decision.artifact_id, None);
assert_eq!(decision.mode, None);
assert_eq!(decision.would_have_verdict, Some(Verdict::Allow));
assert!(!decision.undecided);
}
#[test]
fn nothing_at_all_is_undecided_and_an_allow_is_not() {
assert!(decision(&[]).undecided);
assert!(
!decision(&[contribution("a", MODE_ENFORCE, Verdict::Allow)]).undecided,
"an artifact may allow on purpose"
);
}
#[test]
fn a_tie_breaks_on_artifact_id_and_not_on_iteration_order() {
let forward = decision(&[
contribution("a", MODE_ENFORCE, Verdict::Block),
contribution("b", MODE_ENFORCE, Verdict::Block),
]);
let reversed = decision(&[
contribution("b", MODE_ENFORCE, Verdict::Block),
contribution("a", MODE_ENFORCE, Verdict::Block),
]);
assert_eq!(forward.artifact_id.as_deref(), Some("a"));
assert_eq!(forward, reversed, "a replay names the same rule");
}
#[test]
fn r10_gives_the_single_rewrite_to_the_first_enforcing_optimize_with_a_lever() {
let mut without = contribution("a-no-lever", MODE_ENFORCE, Verdict::Optimize);
without.lever = None;
let mut first = contribution("b-steer", MODE_ENFORCE, Verdict::Optimize);
first.lever = Some(Lever("steer".to_string()));
first.steer_instruction = Some("push a branch instead".to_string());
let mut second = contribution("c-clamp", MODE_ENFORCE, Verdict::Optimize);
second.lever = Some(Lever("effort_clamp".to_string()));
let rewrite = decision(&[without, first, second])
.rewrite
.expect("the first lever-bearing optimize owns it");
assert_eq!(rewrite.lever.0, "steer");
assert_eq!(rewrite.artifact_id.as_deref(), Some("b-steer"));
assert_eq!(
rewrite.steer_instruction.as_deref(),
Some("push a branch instead")
);
}
#[test]
fn a_monitor_artifact_never_spends_r10s_single_slot() {
let mut monitored = contribution("a-monitor", MODE_MONITOR, Verdict::Optimize);
monitored.lever = Some(Lever("effort_clamp".to_string()));
let mut enforced = contribution("b-steer", MODE_ENFORCE, Verdict::Optimize);
enforced.lever = Some(Lever("steer".to_string()));
let rewrite = decision(&[monitored.clone(), enforced])
.rewrite
.expect("the enforcing one owns it");
assert_eq!(rewrite.artifact_id.as_deref(), Some("b-steer"));
assert_eq!(
decision(&[monitored]).rewrite,
None,
"a monitor-only bundle applies nothing to the agent"
);
}
#[test]
fn the_hold_and_the_ground_key_come_from_the_artifact_that_decided() {
let mut monitored = contribution("a-monitor", MODE_MONITOR, Verdict::Block);
monitored.exception_ground_key = Some("m".repeat(64));
let mut enforced = contribution("b-enforce", MODE_ENFORCE, Verdict::Ask);
enforced.exception_ground_key = Some("e".repeat(64));
let decision = decision(&[monitored, enforced]);
assert_eq!(decision.ground_key, Some("e".repeat(64)));
assert_eq!(decision.hold, None);
}
#[test]
fn inconclusive_facts_are_sorted_deduplicated_and_gathered_from_every_artifact() {
let mut monitored = contribution("a", MODE_MONITOR, Verdict::Allow);
monitored.inconclusive = vec!["change_ticket".to_string(), "approved_domains".to_string()];
let mut enforced = contribution("b", MODE_ENFORCE, Verdict::Allow);
enforced.inconclusive = vec!["change_ticket".to_string()];
assert_eq!(
decision(&[monitored, enforced]).inconclusive_facts,
vec!["approved_domains".to_string(), "change_ticket".to_string()],
"a monitor artifact that could not read a fact still found the gap"
);
}
#[test]
fn anomalies_record_from_every_artifact_and_carry_their_own_provenance() {
let mut monitored = contribution("a", MODE_MONITOR, Verdict::Allow);
monitored.anomalies = vec!["burst".to_string()];
let anomalies = decision(&[monitored]).anomalies;
assert_eq!(anomalies.len(), 1);
assert_eq!(anomalies[0].code, "burst");
assert_eq!(anomalies[0].artifact_id.as_deref(), Some("a"));
assert_eq!(anomalies[0].atom_id.as_deref(), Some("atom-a"));
}
}