pub mod approvals;
pub mod consent;
pub mod defaults;
pub mod effects;
pub mod engine;
pub mod input;
pub mod storage;
pub mod types;
pub use defaults::{
install_default_policy, remove_stale_config_consent_policy, seed_declarative_approvals,
};
pub use engine::{CompiledPolicy, compile, evaluate_decision};
pub use input::build_policy_input;
pub use storage::load_active_for_context;
pub use types::{
Consumer, Discloses, Disposition, Exposure, PolicyDecision, PolicyInput, PolicyModule,
PolicyRequest, RequireConsent, SideEffectLevel, StepUp, TaskClass,
};
pub fn decide(policies: &[(i32, CompiledPolicy)], input: &PolicyInput) -> PolicyDecision {
let mut ordered: Vec<&(i32, CompiledPolicy)> = policies.iter().collect();
ordered.sort_by_key(|(priority, _)| std::cmp::Reverse(*priority));
for (_priority, compiled) in ordered {
match engine::evaluate_decision(compiled, input) {
Ok(Some(decision)) => return decision,
Ok(None) => continue,
Err(e) => {
return PolicyDecision::default_deny(format!(
"policy {} evaluation failed: {e}",
compiled.id()
));
}
}
}
PolicyDecision::default_deny("no active policy returned a decision (default-deny)")
}
#[cfg(test)]
mod tests {
use super::*;
fn input(side_effects: SideEffectLevel) -> PolicyInput {
PolicyInput {
request: PolicyRequest {
type_uri: "https://trusttasks.org/spec/vault/release/0.1".into(),
kind: Some("release".into()),
subject: None,
payload_digest: None,
side_effects,
exposure: Exposure {
discloses: Discloses::Secret,
acts_as_subject: false,
},
},
site: None,
context_id: "ctx1".into(),
consumer: Consumer {
did: "did:key:zTest".into(),
kind: None,
device_id: None,
last_user_verification_at: None,
network_class: None,
acr: None,
amr: vec![],
},
}
}
fn policy(src: &str, id: &str) -> CompiledPolicy {
compile(src, id).expect("test policy compiles")
}
#[test]
fn empty_policy_set_denies() {
let d = decide(&[], &input(SideEffectLevel::Mutating));
assert_eq!(d.decision, Disposition::Deny);
}
#[test]
fn all_abstain_denies() {
let p = policy(
r#"package vta.policy
import rego.v1
decision := {"decision": "deny"} if input.request.sideEffects == "destructive""#,
"only-destructive",
);
let d = decide(&[(0, p)], &input(SideEffectLevel::Mutating));
assert_eq!(d.decision, Disposition::Deny);
assert!(d.explanation.unwrap().contains("default-deny"));
}
#[test]
fn higher_priority_override_wins_over_broad_allow() {
let broad_allow = policy(
r#"package vta.policy
import rego.v1
decision := {"decision": "allow"}"#,
"broad-allow",
);
let secret_stepup = policy(
r#"package vta.policy
import rego.v1
decision := {"decision": "requireStepUp", "stepUp": {"method": "pushApproval"}} if {
input.request.exposure.discloses == "secret"
}"#,
"secret-stepup",
);
let d = decide(
&[(0, broad_allow), (10, secret_stepup)],
&input(SideEffectLevel::Mutating),
);
assert_eq!(
d.decision,
Disposition::RequireStepUp,
"the higher-priority secret override must win over the broad allow"
);
}
}