Skip to main content

comparison_opa/
comparison_opa.rs

1//! URGE side of the OPA comparison (`examples/comparison_opa/` at the repo
2//! root holds the Rego side and the write-up).
3//!
4//! Scenario: a payment agent wants to run `submit_payment`. Policy:
5//!   1. the agent must be authorized                      (deontic O)
6//!   2. the compliance review must be complete throughout (temporal G)
7//!   3. the review itself is an obligation with a deadline (lifecycle)
8//!
9//! OPA answers allow/deny for 1+2 collapsed into booleans. URGE evaluates
10//! each constraint in its own logic, cross-checks the results, and — for the
11//! deadline — tracks the obligation over time and emits a violation event.
12
13use urge_core::engine::{ContextValue, EvalContext};
14use urge_meta::{GovernancePipeline, PipelineConfig};
15use urge_monitor::obligation::{Obligation, ObligationEvent, ObligationManager, ObligationType};
16
17const POLICY: &str = "must authorized and always review_completed";
18
19fn evaluate(pipeline: &GovernancePipeline, authorized: bool, review_completed: bool) {
20    let slots = &[
21        ("authorized", ContextValue::Bool(authorized)),
22        ("review_completed", ContextValue::Bool(review_completed)),
23    ];
24    let ctx = EvalContext {
25        slots,
26        logical_time: 0,
27        depth_limit: 16,
28    };
29    let v = pipeline.evaluate_str(POLICY, &ctx);
30
31    println!(
32        "input: authorized={authorized}, review_completed={review_completed}\n\
33         → verdict:    {}\n\
34         → formal:     {}\n\
35         → confidence: {:.0}%\n\
36         → consistent: {} (conflicts: {}{})",
37        if v.valid { "PERMIT" } else { "DENY" },
38        v.formal_notation,
39        v.confidence.as_f32() * 100.0,
40        v.cross_validation.consistent,
41        v.cross_validation.conflicts_detected,
42        v.cross_validation
43            .conflict_detail
44            .map(|d| format!(" — {d}"))
45            .unwrap_or_default(),
46    );
47    println!("→ trace ({} entries), last stages:", v.trace.len());
48    for e in v.trace.entries.iter().rev().take(4).rev() {
49        println!("    [{:?}] {}", e.stage, e.description);
50    }
51    println!();
52}
53
54fn main() {
55    println!("=== URGE side of the OPA comparison ===\n");
56    println!("policy expression: {POLICY}\n");
57
58    let pipeline = GovernancePipeline::new(PipelineConfig::healthcare());
59
60    // Case 1: everything in order — same PERMIT OPA gives.
61    evaluate(&pipeline, true, true);
62
63    // Case 2: review missing. OPA says deny. URGE says deny AND reports the
64    // cross-paradigm finding: the temporal constraint is violated while the
65    // deontic obligation is active — a contradiction, not just a false.
66    evaluate(&pipeline, true, false);
67
68    // Part 2 — what OPA has no vocabulary for at all: the review is an
69    // obligation with a deadline, tracked across time.
70    println!("=== Obligation lifecycle (no OPA equivalent) ===\n");
71    let mut mgr = ObligationManager::new();
72    mgr.register(
73        Obligation::new(
74            "review-P123",
75            ObligationType::Obligatory,
76            "payment-agent",
77            "complete_review",
78            Some(1000), // deadline at t=1000
79            0,
80        ),
81        0,
82    );
83    println!("t=0     obligation registered: complete_review, deadline t=1000");
84    println!(
85        "        active={} violated={}",
86        mgr.active_count(),
87        mgr.violated_count()
88    );
89
90    let violations = mgr.process(ObligationEvent::TimeTick { now: 2000 });
91    println!("t=2000  time tick — deadline exceeded, review never happened");
92    for v in &violations {
93        println!("        VIOLATION EVENT: {v:?}");
94    }
95    println!(
96        "        active={} violated={}",
97        mgr.active_count(),
98        mgr.violated_count()
99    );
100}