Policy Evaluation
Loading an Evaluator
#![allow(unused)] fn main() { use parallax_policy::{PolicyEvaluator, load_rules_from_yaml}; use parallax_query::IndexStats; let rules = load_rules_from_yaml(Path::new("rules/security.yaml"))?; let stats = IndexStats::new(type_counts, class_counts, total, rel_total); let evaluator = PolicyEvaluator::load(rules, &stats)?; // Err if any rule contains invalid PQL (INV-P06) }
Running an Evaluation
Sequential
#![allow(unused)] fn main() { let snap = engine.snapshot(); let graph = GraphReader::new(&snap); let results = evaluator.evaluate_all(&graph, QueryLimits::default()); }
Parallel (3E)
#![allow(unused)] fn main() { // Each rule runs on its own OS thread; results collected in definition order. let results = evaluator.par_evaluate_all(&graph, QueryLimits::default()); }
Both methods return identical results in the same order. par_evaluate_all
is preferred when evaluating many rules — it uses std::thread::scope so
non-'static lifetimes (including GraphReader<'snap>) work correctly.
RuleResult
#![allow(unused)] fn main() { pub struct RuleResult { pub rule_id: String, pub status: RuleStatus, pub violations: Vec<Violation>, pub error: Option<String>, // set on RuleStatus::Error pub evaluated_at: Timestamp, pub duration: Duration, } pub enum RuleStatus { Pass, // query returned 0 results Fail, // query returned ≥1 results Error, // rule evaluation errored (INV-P03: others still run) Skipped, // rule.enabled = false } pub struct Violation { pub entity_id: EntityId, pub entity_type: EntityType, pub display_name: CompactString, pub details: String, } }
INV-P01: Snapshot Atomicity
All rules in one evaluate_all / par_evaluate_all call read the same
snapshot. Rules see a consistent point-in-time view of the graph even if
new data is being ingested concurrently.
INV-P02: Read-Only
Policy evaluation never modifies the graph.
INV-P03: Error Isolation
A rule that errors during evaluation is recorded with RuleStatus::Error and
does not abort evaluation of other rules:
#![allow(unused)] fn main() { for result in &results { match result.status { RuleStatus::Pass => println!("✓ {} — PASS", result.rule_id), RuleStatus::Fail => println!("✗ {} — {} violations", result.rule_id, result.violations.len()), RuleStatus::Error => println!("! {} — {}", result.rule_id, result.error.as_deref().unwrap_or("?")), RuleStatus::Skipped => println!("- {} — skipped", result.rule_id), } } }
Performance
Each rule runs one PQL query. For N rules, evaluate_all makes N sequential
graph reads; par_evaluate_all runs all N concurrently on separate threads.
Typical throughput (100 rules, 100K entities):
- Sequential: ~1–5 seconds (depends on rule complexity and graph structure)
- Parallel: ~100–500ms (bounded by the slowest rule)