pub mod effects;
pub mod evaluate;
pub mod execute;
pub mod facts;
pub mod invariant;
pub mod verdict;
pub mod verify;
pub use effects::{EffectPlan, plan};
pub use evaluate::evaluate;
pub use execute::{AdmitOutcome, DepartOutcome, EffectOutcome, RemintOutcome, apply};
pub use facts::{
Actor, Context, Credential, CredentialStatus, Evidence, Facts, Invitation, MemberState,
Presentation, Purpose, State, Subject,
};
pub use invariant::{Invariant, InvariantViolation};
pub use verdict::{Allow, Deny, Refer, RequestMore, Verdict};
pub use verify::{VerifiedFacts, VerifyError};
use crate::policy::engine::CompiledPolicy;
use vti_common::error::AppError;
pub fn decide(verified: &VerifiedFacts, policy: &CompiledPolicy) -> Result<Verdict, AppError> {
let proposed = evaluate(verified, policy)?;
match invariant::enforce(verified.facts(), proposed) {
Ok(verdict) => Ok(verdict),
Err(violation) => {
tracing::warn!(
purpose = verified.purpose().as_str(),
invariant = violation.invariant.code(),
detail = %violation.detail,
"ceremony policy decision vetoed by host invariant",
);
Ok(violation.into_deny())
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::engine::compile;
use serde_json::json;
use uuid::Uuid;
fn join_facts() -> VerifiedFacts {
let facts = Facts {
purpose: Purpose::Join,
now: "2026-05-30T12:00:00Z".parse().unwrap(),
actor: Actor {
did: "did:key:zHuman".into(),
role: None,
authenticated: true,
},
subject: Subject {
did: "did:key:zHuman".into(),
},
context: Context {
community_did: "did:webvh:acme.example".into(),
channel: "rest".into(),
member_count: 10,
},
evidence: Evidence::default(),
state: State::default(),
};
VerifiedFacts::assemble(facts).expect("verified")
}
#[test]
fn decide_vetoes_a_join_policy_that_grants_admin() {
const ROGUE_JOIN: &str = r#"
package vtc.join
import future.keywords.if
default decision := {"effect": "deny", "with": {"code": "no-matching-route"}}
# A misconfigured policy that tries to hand out admin on join.
decision := {"effect": "allow", "with": {"role": "admin"}} if {
true
}
"#;
let policy = compile(ROGUE_JOIN, Uuid::new_v4()).unwrap();
let verdict = decide(&join_facts(), &policy).expect("decide");
match verdict {
Verdict::Deny(d) => assert_eq!(d.code, "privilege-ceiling"),
other => panic!("expected host-vetoed deny, got {other:?}"),
}
}
#[test]
fn decide_passes_a_well_formed_member_grant() {
const GOOD_JOIN: &str = r#"
package vtc.join
import future.keywords.if
default decision := {"effect": "deny", "with": {"code": "no-matching-route"}}
decision := {"effect": "allow", "with": {"role": "member"}} if {
input.actor.authenticated == true
}
"#;
let policy = compile(GOOD_JOIN, Uuid::new_v4()).unwrap();
let verdict = decide(&join_facts(), &policy).expect("decide");
assert_eq!(
verdict,
Verdict::Allow(Allow {
role: Some("member".into()),
..Default::default()
})
);
}
#[test]
fn decide_returns_policy_deny_untouched() {
const DENY_JOIN: &str = r#"
package vtc.join
default decision := {"effect": "deny", "with": {"code": "closed"}}
"#;
let policy = compile(DENY_JOIN, Uuid::new_v4()).unwrap();
let verdict = decide(&join_facts(), &policy).expect("decide");
assert_eq!(
verdict,
Verdict::from_decision(json!({ "effect": "deny", "with": { "code": "closed" } }))
.unwrap()
);
}
}