sea_core/policy/
core.rs

1use super::expression::{BinaryOp, Expression, UnaryOp};
2use super::violation::{Severity, Violation};
3use crate::graph::Graph;
4use crate::policy::ThreeValuedBool;
5use crate::units::get_default_registry;
6use crate::{ConceptId, SemanticVersion};
7use rust_decimal::prelude::{FromPrimitive, FromStr};
8use rust_decimal::Decimal;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
12pub enum PolicyModality {
13    Obligation,
14    Prohibition,
15    Permission,
16}
17
18/// Backwards-compatible alias used by existing tests and documentation
19#[allow(unused_imports)]
20pub use PolicyModality as DeonticModality;
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23pub enum PolicyKind {
24    Constraint,
25    Derivation,
26    Obligation,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct Policy {
31    pub id: ConceptId,
32    pub name: String,
33    pub namespace: String,
34    pub version: SemanticVersion,
35    expression: Expression,
36    pub modality: PolicyModality,
37    pub kind: PolicyKind,
38    pub priority: i32,
39    pub rationale: Option<String>,
40    pub tags: Vec<String>,
41    #[serde(skip)]
42    cached_normalized_expr: std::sync::OnceLock<super::NormalizedExpression>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
46pub struct EvaluationResult {
47    /// Backwards compatible boolean representing whether a policy was satisfied.
48    /// Defaults to `false` if the evaluator produced an unknown (NULL) result.
49    pub is_satisfied: bool,
50    /// Tri-state evaluation result: Some(true/false) or None when evaluation is unknown.
51    pub is_satisfied_tristate: Option<bool>,
52    pub violations: Vec<Violation>,
53}
54
55impl Policy {
56    /// Returns the normalized canonical form of this policy's expression.
57    ///
58    /// Useful for caching, equivalence checking, and deterministic display.
59    #[must_use]
60    pub fn normalized_expression(&self) -> &super::NormalizedExpression {
61        self.cached_normalized_expr
62            .get_or_init(|| self.expression.normalize())
63    }
64
65    /// Get the policy expression.
66    pub fn expression(&self) -> &Expression {
67        &self.expression
68    }
69
70    /// Set the policy expression and invalidate the normalized cache.
71    pub fn set_expression(&mut self, expr: Expression) {
72        self.expression = expr;
73        // Invalidate cache by creating a new empty lock
74        self.cached_normalized_expr = std::sync::OnceLock::new();
75    }
76
77    pub fn new(name: impl Into<String>, expression: Expression) -> Self {
78        let name = name.into();
79        Self {
80            id: ConceptId::from_concept("default", &name),
81            name,
82            namespace: "default".to_string(),
83            version: SemanticVersion::default(),
84            expression,
85            modality: PolicyModality::Obligation,
86            kind: PolicyKind::Constraint,
87            priority: 0,
88            rationale: None,
89            tags: Vec::new(),
90            cached_normalized_expr: std::sync::OnceLock::new(),
91        }
92    }
93
94    pub fn new_with_namespace(
95        name: impl Into<String>,
96        namespace: impl Into<String>,
97        expression: Expression,
98    ) -> Self {
99        let namespace = namespace.into();
100        let name = name.into();
101        let id = ConceptId::from_concept(&namespace, &name);
102
103        Self {
104            id,
105            name,
106            namespace,
107            version: SemanticVersion::default(),
108            expression,
109            modality: PolicyModality::Obligation,
110            kind: PolicyKind::Constraint,
111            priority: 0,
112            rationale: None,
113            tags: Vec::new(),
114            cached_normalized_expr: std::sync::OnceLock::new(),
115        }
116    }
117
118    pub fn with_modality(mut self, modality: PolicyModality) -> Self {
119        self.modality = modality;
120        self
121    }
122
123    pub fn with_version(mut self, version: SemanticVersion) -> Self {
124        self.version = version;
125        self
126    }
127
128    pub fn with_kind(mut self, kind: PolicyKind) -> Self {
129        self.kind = kind;
130        self
131    }
132
133    pub fn with_priority(mut self, priority: i32) -> Self {
134        self.priority = priority;
135        self
136    }
137
138    pub fn with_rationale(mut self, rationale: impl Into<String>) -> Self {
139        self.rationale = Some(rationale.into());
140        self
141    }
142
143    pub fn with_tags<I, S>(mut self, tags: I) -> Self
144    where
145        I: IntoIterator<Item = S>,
146        S: Into<String>,
147    {
148        self.tags = tags.into_iter().map(Into::into).collect();
149        self
150    }
151
152    pub fn with_metadata(
153        mut self,
154        kind: Option<PolicyKind>,
155        modality: Option<PolicyModality>,
156        priority: Option<i32>,
157        rationale: Option<String>,
158        tags: Vec<String>,
159    ) -> Self {
160        if let Some(kind) = kind {
161            self.kind = kind;
162        }
163        if let Some(modality) = modality {
164            self.modality = modality;
165        }
166        if let Some(priority) = priority {
167            self.priority = priority;
168        }
169        self.rationale = rationale;
170        if !tags.is_empty() {
171            self.tags = tags;
172        }
173        self
174    }
175
176    pub fn kind(&self) -> &PolicyKind {
177        &self.kind
178    }
179
180    pub fn evaluate(&self, graph: &Graph) -> Result<EvaluationResult, String> {
181        self.evaluate_with_mode(graph, graph.use_three_valued_logic())
182    }
183
184    pub fn evaluate_with_mode(
185        &self,
186        graph: &Graph,
187        use_three_valued_logic: bool,
188    ) -> Result<EvaluationResult, String> {
189        Self::validate_aggregation_usage(&self.expression, true)?;
190
191        let expanded = self.expression.expand(graph)?;
192
193        // Evaluate expression; runtime toggle chooses three-valued vs boolean path.
194        // We compute the tri-state result and derive a backward-compatible boolean (false when Null).
195        let is_satisfied_tristate: Option<bool> = if use_three_valued_logic {
196            match self.evaluate_expression_three_valued(&expanded, graph)? {
197                ThreeValuedBool::True => Some(true),
198                ThreeValuedBool::False => Some(false),
199                ThreeValuedBool::Null => None,
200            }
201        } else {
202            Some(self.evaluate_expression_boolean(&expanded, graph)?)
203        };
204
205        let is_satisfied = is_satisfied_tristate.unwrap_or(false);
206
207        let violations = if is_satisfied_tristate == Some(true) {
208            vec![]
209        } else if is_satisfied_tristate == Some(false) {
210            vec![Violation::new(
211                &self.name,
212                format!("Policy '{}' was violated", self.name),
213                self.modality.to_severity(),
214            )]
215        } else {
216            // Unknown (NULL) evaluation: severity follows the policy modality.
217            vec![Violation::new(
218                &self.name,
219                format!("Policy '{}' evaluation is UNKNOWN (NULL)", self.name),
220                self.modality.to_severity(),
221            )]
222        };
223
224        Ok(EvaluationResult {
225            is_satisfied,
226            is_satisfied_tristate,
227            violations,
228        })
229    }
230
231    fn validate_aggregation_usage(
232        expr: &Expression,
233        in_boolean_context: bool,
234    ) -> Result<(), String> {
235        match expr {
236            Expression::Aggregation { .. } | Expression::AggregationComprehension { .. } => {
237                if in_boolean_context {
238                    Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string())
239                } else {
240                    Ok(())
241                }
242            }
243            Expression::Binary { op, left, right } => {
244                let child_boolean = matches!(op, BinaryOp::And | BinaryOp::Or);
245                Self::validate_aggregation_usage(left, child_boolean)?;
246                Self::validate_aggregation_usage(right, child_boolean)?;
247                Ok(())
248            }
249            Expression::Unary { op, operand } => {
250                let child_boolean = matches!(op, UnaryOp::Not);
251                Self::validate_aggregation_usage(operand, child_boolean)
252            }
253            Expression::Quantifier {
254                collection,
255                condition,
256                ..
257            } => {
258                Self::validate_aggregation_usage(collection, false)?;
259                Self::validate_aggregation_usage(condition, true)
260            }
261            Expression::GroupBy {
262                collection,
263                filter,
264                key,
265                condition,
266                ..
267            } => {
268                Self::validate_aggregation_usage(collection, false)?;
269                if let Some(f) = filter {
270                    Self::validate_aggregation_usage(f, true)?;
271                }
272                Self::validate_aggregation_usage(key, false)?;
273                Self::validate_aggregation_usage(condition, true)
274            }
275            _ => Ok(()),
276        }
277    }
278
279    fn evaluate_expression_boolean(
280        &self,
281        expr: &Expression,
282        graph: &Graph,
283    ) -> Result<bool, String> {
284        match expr {
285            Expression::Literal(v) => v
286                .as_bool()
287                .ok_or_else(|| format!("Expected boolean literal, got: {}", v)),
288            Expression::Variable(name) => {
289                Err(format!("Cannot evaluate unexpanded variable: {}", name))
290            }
291            Expression::Cast { .. } => {
292                let val = Self::get_runtime_value(expr, graph)?;
293                val.as_bool()
294                    .ok_or_else(|| format!("Expected boolean from cast, got: {}", val))
295            }
296            Expression::Binary { op, left, right } => match op {
297                BinaryOp::And | BinaryOp::Or => {
298                    let left_val = self.evaluate_expression_boolean(left, graph)?;
299                    let right_val = self.evaluate_expression_boolean(right, graph)?;
300
301                    Ok(match op {
302                        BinaryOp::And => left_val && right_val,
303                        BinaryOp::Or => left_val || right_val,
304                        _ => unreachable!(),
305                    })
306                }
307                BinaryOp::Equal | BinaryOp::NotEqual => {
308                    self.compare_values(left, right, graph, |l, r| match op {
309                        BinaryOp::Equal => l == r,
310                        BinaryOp::NotEqual => l != r,
311                        _ => unreachable!(),
312                    })
313                }
314                BinaryOp::GreaterThan
315                | BinaryOp::LessThan
316                | BinaryOp::GreaterThanOrEqual
317                | BinaryOp::LessThanOrEqual => {
318                    self.compare_numeric(left, right, graph, |l, r| match op {
319                        BinaryOp::GreaterThan => l > r,
320                        BinaryOp::LessThan => l < r,
321                        BinaryOp::GreaterThanOrEqual => l >= r,
322                        BinaryOp::LessThanOrEqual => l <= r,
323                        _ => unreachable!(),
324                    })
325                }
326                BinaryOp::Plus | BinaryOp::Minus | BinaryOp::Multiply | BinaryOp::Divide => {
327                    Err("Arithmetic operations not supported in boolean context".to_string())
328                }
329                BinaryOp::Contains | BinaryOp::StartsWith | BinaryOp::EndsWith => self
330                    .compare_strings(left, right, graph, |l, r| match op {
331                        BinaryOp::Contains => l.contains(r),
332                        BinaryOp::StartsWith => l.starts_with(r),
333                        BinaryOp::EndsWith => l.ends_with(r),
334                        _ => unreachable!(),
335                    }),
336                BinaryOp::HasRole => self.evaluate_has_role(left, right, graph),
337                BinaryOp::Matches => self.evaluate_pattern_match(left, right, graph),
338                BinaryOp::Before | BinaryOp::After | BinaryOp::During => {
339                    // Temporal operators - parse and compare ISO 8601 timestamps
340                    let left_str = self.get_string_value(left, graph)?;
341                    let right_str = self.get_string_value(right, graph)?;
342
343                    // Parse timestamps using chrono
344                    let left_dt = chrono::DateTime::parse_from_rfc3339(&left_str).map_err(|e| {
345                        format!("Failed to parse left timestamp '{}': {}", left_str, e)
346                    })?;
347                    let right_dt =
348                        chrono::DateTime::parse_from_rfc3339(&right_str).map_err(|e| {
349                            format!("Failed to parse right timestamp '{}': {}", right_str, e)
350                        })?;
351
352                    let result = match op {
353                        BinaryOp::Before => left_dt < right_dt,
354                        BinaryOp::After => left_dt > right_dt,
355                        BinaryOp::During => {
356                            return Err("'during' operator requires interval semantics which are not yet implemented. Use 'before' and 'after' for timestamp comparisons.".to_string())
357                        }
358                        _ => unreachable!(),
359                    };
360                    Ok(result)
361                }
362            },
363            Expression::Unary { op, operand } => {
364                let val = self.evaluate_expression_boolean(operand, graph)?;
365                Ok(match op {
366                    UnaryOp::Not => !val,
367                    UnaryOp::Negate => {
368                        return Err("Negate operator not supported in boolean context".to_string())
369                    }
370                })
371            }
372            Expression::Quantifier { .. } => {
373                Err("Cannot evaluate non-expanded quantifier".to_string())
374            }
375            Expression::MemberAccess { object, member } => {
376                let value = Self::get_runtime_value(expr, graph)?;
377                match value {
378                    serde_json::Value::Bool(v) => Ok(v),
379                    serde_json::Value::Null => Ok(false),
380                    _ => Err(format!(
381                        "Expected boolean value for member '{}.{}', but found {:?}",
382                        object, member, value
383                    )),
384                }
385            }
386            Expression::Aggregation { .. } => {
387                Err("Cannot evaluate non-expanded aggregation".to_string())
388            }
389            Expression::AggregationComprehension { .. } => {
390                Err("Cannot evaluate non-expanded aggregation comprehension".to_string())
391            }
392            Expression::QuantityLiteral { .. } => {
393                Err("Cannot evaluate quantity literal in boolean context".to_string())
394            }
395            Expression::TimeLiteral(_) => {
396                Err("Cannot evaluate time literal in boolean context".to_string())
397            }
398            Expression::IntervalLiteral { .. } => {
399                Err("Cannot evaluate interval literal in boolean context".to_string())
400            }
401            Expression::GroupBy { .. } => Err("Cannot evaluate non-expanded group_by".to_string()),
402        }
403    }
404
405    fn evaluate_expression_three_valued(
406        &self,
407        expr: &Expression,
408        graph: &Graph,
409    ) -> Result<ThreeValuedBool, String> {
410        use ThreeValuedBool as T;
411
412        match expr {
413            Expression::Literal(v) => Ok(T::from_option_bool(v.as_bool())),
414            Expression::Variable(name) => Err(format!("Cannot evaluate unexpanded variable: {}", name)),
415            Expression::Cast { .. } => {
416                let val = Self::get_runtime_value(expr, graph)?;
417                Ok(T::from_option_bool(val.as_bool()))
418            }
419            Expression::Binary { op, left, right } => match op {
420                BinaryOp::And | BinaryOp::Or => {
421                    let l = self.evaluate_expression_three_valued(left, graph)?;
422                    let r = self.evaluate_expression_three_valued(right, graph)?;
423                    Ok(match op {
424                        BinaryOp::And => l.and(r),
425                        BinaryOp::Or => l.or(r),
426                        _ => unreachable!(),
427                    })
428                }
429                BinaryOp::Equal | BinaryOp::NotEqual => {
430                    let left_val = Self::get_runtime_value(left, graph);
431                    let right_val = Self::get_runtime_value(right, graph);
432                    match (left_val, right_val) {
433                        (Ok(lv), Ok(rv)) => {
434                            // If either operand is JSON Null, the comparison yields Null.
435                            if lv.is_null() || rv.is_null() {
436                                Ok(T::Null)
437                            } else {
438                                let numeric_eq = match (
439                                    Self::parse_numeric_with_unit_value(&lv),
440                                    Self::parse_numeric_with_unit_value(&rv),
441                                ) {
442                                    (Ok(ln), Ok(rn)) => {
443                                        self.normalize_units_nullable(ln, rn)?
444                                            .map(|(l, r)| l == r)
445                                    }
446                                    _ => None,
447                                };
448
449                                let equality = if let Some(eq) = numeric_eq {
450                                    Some(eq)
451                                } else if lv.is_number() || rv.is_number() {
452                                    None
453                                } else {
454                                    Some(lv == rv)
455                                };
456
457                                let eq = match (op, equality) {
458                                    (_, None) => return Ok(T::Null),
459                                    (BinaryOp::Equal, Some(true)) => true,
460                                    (BinaryOp::Equal, Some(false)) => false,
461                                    (BinaryOp::NotEqual, Some(true)) => false,
462                                    (BinaryOp::NotEqual, Some(false)) => true,
463                                    _ => unreachable!(),
464                                };
465
466                                Ok(T::from_option_bool(Some(eq)))
467                            }
468                        }
469                        _ => Ok(T::Null),
470                    }
471                }
472                BinaryOp::GreaterThan
473                | BinaryOp::LessThan
474                | BinaryOp::GreaterThanOrEqual
475                | BinaryOp::LessThanOrEqual => {
476                    let left_v = Self::get_runtime_value(left, graph);
477                    let right_v = Self::get_runtime_value(right, graph);
478                    match (left_v, right_v) {
479                        (Ok(lv), Ok(rv)) => {
480                            if lv.is_null() || rv.is_null() {
481                                Ok(T::Null)
482                            } else {
483                                let numeric = self
484                                    .normalize_units_nullable(
485                                        Self::parse_numeric_with_unit_value(&lv)
486                                            .ok()
487                                            .flatten(),
488                                        Self::parse_numeric_with_unit_value(&rv)
489                                            .ok()
490                                            .flatten(),
491                                    )?
492                                    .map(|(l, r)| match op {
493                                        BinaryOp::GreaterThan => l > r,
494                                        BinaryOp::LessThan => l < r,
495                                        BinaryOp::GreaterThanOrEqual => l >= r,
496                                        BinaryOp::LessThanOrEqual => l <= r,
497                                        _ => unreachable!(),
498                                    });
499
500                                Ok(T::from_option_bool(numeric))
501                            }
502                        }
503                        _ => Ok(T::Null),
504                    }
505                }
506                BinaryOp::Plus | BinaryOp::Minus | BinaryOp::Multiply | BinaryOp::Divide => {
507                    Err("Arithmetic operations not supported in boolean context".to_string())
508                }
509                BinaryOp::Contains | BinaryOp::StartsWith | BinaryOp::EndsWith => {
510                    let left_v = Self::get_runtime_value(left, graph);
511                    let right_v = Self::get_runtime_value(right, graph);
512                    match (left_v, right_v) {
513                        (Ok(lv), Ok(rv)) => {
514                            if lv.is_null() || rv.is_null() {
515                                Ok(T::Null)
516                            } else if let (Some(ls), Some(rs)) = (lv.as_str(), rv.as_str()) {
517                                let ok = match op {
518                                    BinaryOp::Contains => ls.contains(rs),
519                                    BinaryOp::StartsWith => ls.starts_with(rs),
520                                    BinaryOp::EndsWith => ls.ends_with(rs),
521                                    _ => unreachable!(),
522                                };
523                                Ok(T::from_option_bool(Some(ok)))
524                            } else {
525                                Ok(T::Null)
526                            }
527                        }
528                        _ => Ok(T::Null),
529                    }
530                }
531                BinaryOp::HasRole => {
532                    let role_check = self.evaluate_has_role(left, right, graph)?;
533                    Ok(T::from_option_bool(Some(role_check)))
534                }
535                BinaryOp::Matches => {
536                    let left_v = Self::get_runtime_value(left, graph);
537                    let right_v = Self::get_runtime_value(right, graph);
538
539                    match (left_v, right_v) {
540                        (Ok(lv), Ok(rv)) => {
541                            if lv.is_null() || rv.is_null() {
542                                return Ok(T::Null);
543                            }
544
545                            if let (Some(candidate), Some(pattern_name)) = (lv.as_str(), rv.as_str()) {
546                                let pattern = graph
547                                    .find_pattern(pattern_name, Some(&self.namespace))
548                                    .ok_or_else(|| {
549                                        format!(
550                                            "Pattern '{}' not found in namespace '{}'",
551                                            pattern_name, self.namespace
552                                        )
553                                    })?;
554                                let is_match = pattern.is_match(candidate).map_err(|e| {
555                                    format!(
556                                        "Pattern '{}' failed to evaluate: {}",
557                                        pattern_name, e
558                                    )
559                                })?;
560                                Ok(T::from_option_bool(Some(is_match)))
561                            } else {
562                                Ok(T::Null)
563                            }
564                        }
565                        _ => Ok(T::Null),
566                    }
567                }
568                BinaryOp::Before | BinaryOp::After | BinaryOp::During => {
569                    // Temporal operators
570                    if matches!(op, BinaryOp::During) {
571                        return Err("'during' operator requires interval semantics which are not yet implemented. Use 'before' and 'after' for timestamp comparisons.".to_string());
572                    }
573
574                    // Parse and compare ISO 8601 timestamps
575                    let left_v = Self::get_runtime_value(left, graph);
576                    let right_v = Self::get_runtime_value(right, graph);
577                    match (left_v, right_v) {
578                        (Ok(lv), Ok(rv)) => {
579                            if lv.is_null() || rv.is_null() {
580                                Ok(T::Null)
581                            } else if let (Some(ls), Some(rs)) = (lv.as_str(), rv.as_str()) {
582                                // Parse timestamps using chrono
583                                let left_dt = match chrono::DateTime::parse_from_rfc3339(ls) {
584                                    Ok(dt) => dt,
585                                    Err(_) => return Ok(T::Null), // Invalid timestamp -> Null
586                                };
587                                let right_dt = match chrono::DateTime::parse_from_rfc3339(rs) {
588                                    Ok(dt) => dt,
589                                    Err(_) => return Ok(T::Null), // Invalid timestamp -> Null
590                                };
591
592                                let result = match op {
593                                    BinaryOp::Before => left_dt < right_dt,
594                                    BinaryOp::After => left_dt > right_dt,
595                                    _ => unreachable!(),
596                                };
597                                Ok(T::from_option_bool(Some(result)))
598                            } else {
599                                Ok(T::Null)
600                            }
601                        }
602                        _ => Ok(T::Null),
603                    }
604                }
605            },
606            Expression::Unary { op, operand } => {
607                let v = self.evaluate_expression_three_valued(operand, graph)?;
608                Ok(match op {
609                    UnaryOp::Not => v.not(),
610                    UnaryOp::Negate => return Err("Negate operator not supported in boolean context".to_string()),
611                })
612            }
613            Expression::Quantifier { quantifier, variable, collection, condition } => {
614                // Evaluate quantifiers with three-valued semantics
615                let items = Expression::get_collection(collection, graph)?;
616                use super::expression::Quantifier as Q;
617
618                let mut saw_true = 0usize;
619                let mut saw_false = 0usize;
620                let mut saw_null = 0usize;
621
622                for item in items {
623                    let substituted = condition.substitute(variable, &item)?;
624                    let val = self.evaluate_expression_three_valued(&substituted, graph)?;
625                    match val {
626                        T::True => saw_true += 1,
627                        T::False => saw_false += 1,
628                        T::Null => saw_null += 1,
629                    }
630                }
631
632                match quantifier {
633                    Q::ForAll => {
634                        if saw_false > 0 { return Ok(T::False); }
635                        if saw_null > 0 { return Ok(T::Null); }
636                        Ok(T::True)
637                    }
638                    Q::Exists => {
639                        if saw_true > 0 { return Ok(T::True); }
640                        if saw_null > 0 { return Ok(T::Null); }
641                        Ok(T::False)
642                    }
643                    Q::ExistsUnique => {
644                        if saw_true > 1 { return Ok(T::False); }
645                        if saw_true == 1 && saw_null == 0 { return Ok(T::True); }
646                        if saw_true == 1 && saw_null > 0 { return Ok(T::Null); }
647                        if saw_true == 0 && saw_null > 0 { return Ok(T::Null); }
648                        Ok(T::False)
649                    }
650                }
651            }
652            Expression::MemberAccess { object: _, member: _ } => {
653                // Resolve object/member to a runtime value and convert to bool
654                let value = Self::get_runtime_value(expr, graph)?;
655                Ok(T::from_option_bool(value.as_bool()))
656            }
657            Expression::Aggregation { .. } => Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string()),
658            Expression::AggregationComprehension { .. } => Err("Aggregation in boolean context requires explicit comparison (e.g., COUNT(...) > 0)".to_string()),
659            Expression::QuantityLiteral { .. } => Err("Cannot convert quantity to boolean; compare against a threshold instead".to_string()),
660            Expression::TimeLiteral(_) => Err("Cannot convert time to boolean; use temporal comparison operators".to_string()),
661            Expression::IntervalLiteral { .. } => Err("Cannot convert interval to boolean; use temporal comparison operators".to_string()),
662            Expression::GroupBy { .. } => Err("Cannot evaluate non-expanded group_by".to_string()),
663        }
664    }
665
666    fn compare_values<F>(
667        &self,
668        left: &Expression,
669        right: &Expression,
670        graph: &Graph,
671        op: F,
672    ) -> Result<bool, String>
673    where
674        F: Fn(&serde_json::Value, &serde_json::Value) -> bool,
675    {
676        let left_val = self
677            .get_literal_value(left)
678            .or_else(|_| Self::get_runtime_value(left, graph))?;
679        let right_val = self
680            .get_literal_value(right)
681            .or_else(|_| Self::get_runtime_value(right, graph))?;
682        Ok(op(&left_val, &right_val))
683    }
684
685    fn compare_numeric<F>(
686        &self,
687        left: &Expression,
688        right: &Expression,
689        graph: &Graph,
690        op: F,
691    ) -> Result<bool, String>
692    where
693        F: Fn(Decimal, Decimal) -> bool,
694    {
695        let left_val = self.resolve_numeric_with_unit(left, graph)?;
696        let right_val = self.resolve_numeric_with_unit(right, graph)?;
697        let (left_aligned, right_aligned) = self.normalize_units_strict(left_val, right_val)?;
698        Ok(op(left_aligned, right_aligned))
699    }
700
701    fn compare_strings<F>(
702        &self,
703        left: &Expression,
704        right: &Expression,
705        graph: &Graph,
706        op: F,
707    ) -> Result<bool, String>
708    where
709        F: Fn(&str, &str) -> bool,
710    {
711        let left_val = self.get_string_value(left, graph)?;
712        let right_val = self.get_string_value(right, graph)?;
713        Ok(op(&left_val, &right_val))
714    }
715
716    fn evaluate_pattern_match(
717        &self,
718        left: &Expression,
719        right: &Expression,
720        graph: &Graph,
721    ) -> Result<bool, String> {
722        let candidate = self.get_string_value(left, graph)?;
723        let pattern_name = self.get_string_value(right, graph)?;
724
725        let pattern = graph
726            .find_pattern(&pattern_name, Some(&self.namespace))
727            .ok_or_else(|| {
728                format!(
729                    "Pattern '{}' not found in namespace '{}'",
730                    pattern_name, self.namespace
731                )
732            })?;
733
734        pattern
735            .is_match(&candidate)
736            .map_err(|e| format!("Pattern '{}' failed to evaluate: {}", pattern_name, e))
737    }
738
739    fn evaluate_has_role(
740        &self,
741        left: &Expression,
742        right: &Expression,
743        graph: &Graph,
744    ) -> Result<bool, String> {
745        let target_role = self.get_string_value(right, graph)?;
746        let roles = self.collect_roles(left, graph)?;
747
748        Ok(roles
749            .iter()
750            .any(|role| role.eq_ignore_ascii_case(&target_role)))
751    }
752
753    fn collect_roles(&self, expr: &Expression, graph: &Graph) -> Result<Vec<String>, String> {
754        let value = Self::get_runtime_value(expr, graph)?;
755
756        if let Some(arr) = value.as_array() {
757            return Ok(arr
758                .iter()
759                .filter_map(|v| v.as_str().map(|s| s.to_string()))
760                .collect());
761        }
762
763        if let Some(obj) = value.as_object() {
764            if let Some(roles) = obj.get("roles").and_then(|r| r.as_array()) {
765                return Ok(roles
766                    .iter()
767                    .filter_map(|v| v.as_str().map(|s| s.to_string()))
768                    .collect());
769            }
770
771            if let Some(name) = obj.get("name").and_then(|v| v.as_str()) {
772                if let Some(entity_id) = graph.find_entity_by_name(name) {
773                    return Ok(graph.role_names_for_entity(&entity_id));
774                }
775            }
776        }
777
778        if let Some(name) = value.as_str() {
779            if let Some(entity_id) = graph.find_entity_by_name(name) {
780                return Ok(graph.role_names_for_entity(&entity_id));
781            }
782
783            if let Some(role_id) = graph.find_role_by_name(name) {
784                if let Some(role) = graph.get_role(&role_id) {
785                    return Ok(vec![role.name().to_string()]);
786                }
787            }
788        }
789
790        Ok(Vec::new())
791    }
792
793    fn get_literal_value(&self, expr: &Expression) -> Result<serde_json::Value, String> {
794        match expr {
795            Expression::Literal(v) => Ok(v.clone()),
796            _ => Err("Expected literal value".to_string()),
797        }
798    }
799
800    fn parse_decimal_value(value: &serde_json::Value) -> Result<Decimal, String> {
801        if let Some(s) = value.as_str() {
802            Decimal::from_str(s).map_err(|e| e.to_string())
803        } else if let Some(f) = value.as_f64() {
804            Decimal::from_f64(f).ok_or_else(|| format!("Unable to represent {} as Decimal", f))
805        } else if let Some(i) = value.as_i64() {
806            Ok(Decimal::from(i))
807        } else if let Some(u) = value.as_u64() {
808            Decimal::from_u64(u).ok_or_else(|| format!("Unable to represent {} as Decimal", u))
809        } else {
810            Err(format!("Expected numeric value, got: {}", value))
811        }
812    }
813
814    fn parse_numeric_with_unit_value(
815        value: &serde_json::Value,
816    ) -> Result<Option<(Decimal, Option<String>)>, String> {
817        if let Some(obj) = value.as_object() {
818            match (obj.get("__quantity_value"), obj.get("__quantity_unit")) {
819                (Some(q_val), Some(q_unit)) => {
820                    let unit_str = q_unit
821                        .as_str()
822                        .ok_or_else(|| {
823                            format!("Expected __quantity_unit to be string, got: {}", q_unit)
824                        })?
825                        .to_string();
826                    let value_dec = Self::parse_decimal_value(q_val)
827                        .map_err(|e| format!("Invalid __quantity_value: {}", e))?;
828                    return Ok(Some((value_dec, Some(unit_str))));
829                }
830                (Some(_), None) | (None, Some(_)) => {
831                    return Err(
832                        "Quantity object must include both __quantity_value and __quantity_unit"
833                            .to_string(),
834                    )
835                }
836                _ => {}
837            }
838        }
839
840        if value.is_number() || value.is_string() {
841            return Ok(Some((Self::parse_decimal_value(value)?, None)));
842        }
843
844        Ok(None)
845    }
846
847    fn resolve_numeric_with_unit(
848        &self,
849        expr: &Expression,
850        graph: &Graph,
851    ) -> Result<(Decimal, Option<String>), String> {
852        let value = self
853            .get_literal_value(expr)
854            .or_else(|_| Self::get_runtime_value(expr, graph))?;
855        Self::parse_numeric_with_unit_value(&value)?
856            .ok_or_else(|| format!("Expected numeric value, got: {}", value))
857    }
858
859    fn normalize_units_strict(
860        &self,
861        left: (Decimal, Option<String>),
862        right: (Decimal, Option<String>),
863    ) -> Result<(Decimal, Decimal), String> {
864        match (left.1, right.1) {
865            (Some(l_unit), Some(r_unit)) => {
866                let registry = get_default_registry();
867                let registry = registry
868                    .read()
869                    .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
870
871                if l_unit == r_unit {
872                    Ok((left.0, right.0))
873                } else {
874                    let from = registry
875                        .get_unit(&r_unit)
876                        .map_err(|e| format!("Invalid unit '{}': {}", r_unit, e))?;
877                    let to = registry
878                        .get_unit(&l_unit)
879                        .map_err(|e| format!("Invalid unit '{}': {}", l_unit, e))?;
880                    let converted = registry
881                        .convert(right.0, from, to)
882                        .map_err(|e| format!("Unit conversion failed: {}", e))?;
883                    Ok((left.0, converted))
884                }
885            }
886            (None, None) => Ok((left.0, right.0)),
887            (Some(l_unit), None) => Err(format!(
888                "Cannot compare quantity with unit '{}' to unitless value",
889                l_unit
890            )),
891            (None, Some(r_unit)) => Err(format!(
892                "Cannot compare unitless value to quantity with unit '{}'",
893                r_unit
894            )),
895        }
896    }
897
898    fn normalize_units_nullable(
899        &self,
900        left: Option<(Decimal, Option<String>)>,
901        right: Option<(Decimal, Option<String>)>,
902    ) -> Result<Option<(Decimal, Decimal)>, String> {
903        let (left, right) = match (left, right) {
904            (Some(l), Some(r)) => (l, r),
905            _ => return Ok(None),
906        };
907
908        match (left.1.clone(), right.1.clone()) {
909            (Some(l_unit), Some(r_unit)) => {
910                let registry = get_default_registry();
911                let registry = registry
912                    .read()
913                    .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
914                if l_unit == r_unit {
915                    Ok(Some((left.0, right.0)))
916                } else {
917                    let from = registry.get_unit(&r_unit);
918                    let to = registry.get_unit(&l_unit);
919                    if let (Ok(from), Ok(to)) = (from, to) {
920                        match registry.convert(right.0, from, to) {
921                            Ok(converted) => Ok(Some((left.0, converted))),
922                            Err(_) => Ok(None),
923                        }
924                    } else {
925                        Ok(None)
926                    }
927                }
928            }
929            (None, None) => Ok(Some((left.0, right.0))),
930            _ => Ok(None),
931        }
932    }
933
934    fn get_string_value(&self, expr: &Expression, graph: &Graph) -> Result<String, String> {
935        let v = self
936            .get_literal_value(expr)
937            .or_else(|_| Self::get_runtime_value(expr, graph))?;
938        v.as_str()
939            .map(|s| s.to_string())
940            .ok_or_else(|| "Expected string value".to_string())
941    }
942
943    fn get_runtime_value(expr: &Expression, graph: &Graph) -> Result<serde_json::Value, String> {
944        match expr {
945            Expression::Literal(v) => Ok(v.clone()),
946            Expression::MemberAccess { object, member } => {
947                // Try resolving as entity
948                if let Some(id) = graph.find_entity_by_name(object) {
949                    if let Some(entity) = graph.get_entity(&id) {
950                        // Special known members
951                        if member == "id" {
952                            return Ok(serde_json::json!(entity.id().to_string()));
953                        } else if member == "name" {
954                            return Ok(serde_json::json!(entity.name()));
955                        } else if member == "namespace" {
956                            return Ok(serde_json::json!(entity.namespace()));
957                        }
958                        if let Some(val) = entity.get_attribute(member) {
959                            if val.is_null() {
960                                log::debug!(
961                                    "Entity '{}' member '{}' present but NULL; returning Null",
962                                    object,
963                                    member
964                                );
965                            }
966                            return Ok(val.clone());
967                        }
968                        log::debug!(
969                            "Entity '{}' found but member '{}' missing; returning Null",
970                            object,
971                            member
972                        );
973                        return Ok(serde_json::Value::Null);
974                    } else {
975                        log::debug!(
976                            "Entity lookup for '{}' returned id {} but entity missing; returning Null",
977                            object, id
978                        );
979                    }
980                } else {
981                    log::debug!(
982                        "Entity '{}' not found while resolving member '{}'; continuing lookup",
983                        object,
984                        member
985                    );
986                }
987
988                if let Some(id) = graph.find_resource_by_name(object) {
989                    if let Some(resource) = graph.get_resource(&id) {
990                        if member == "id" {
991                            return Ok(serde_json::json!(resource.id().to_string()));
992                        } else if member == "name" {
993                            return Ok(serde_json::json!(resource.name()));
994                        } else if member == "unit" {
995                            return Ok(serde_json::json!(resource.unit()));
996                        }
997                        if let Some(val) = resource.get_attribute(member) {
998                            if val.is_null() {
999                                log::debug!(
1000                                    "Resource '{}' member '{}' present but NULL; returning Null",
1001                                    object,
1002                                    member
1003                                );
1004                            }
1005                            return Ok(val.clone());
1006                        }
1007                        log::debug!(
1008                            "Resource '{}' found but member '{}' missing; returning Null",
1009                            object,
1010                            member
1011                        );
1012                        return Ok(serde_json::Value::Null);
1013                    } else {
1014                        log::debug!(
1015                            "Resource lookup for '{}' returned id {} but resource missing; returning Null",
1016                            object, id
1017                        );
1018                    }
1019                } else {
1020                    log::debug!(
1021                        "Resource '{}' not found while resolving member '{}'; returning Null",
1022                        object,
1023                        member
1024                    );
1025                }
1026
1027                // Not found: return Null to indicate unknown member
1028                log::debug!(
1029                    "Member access '{}.{}' did not resolve to entity or resource; returning Null",
1030                    object,
1031                    member
1032                );
1033                Ok(serde_json::Value::Null)
1034            }
1035            Expression::Aggregation {
1036                function,
1037                collection,
1038                field,
1039                filter,
1040            } => {
1041                let v =
1042                    Expression::evaluate_aggregation(function, collection, field, filter, graph)?;
1043                Ok(v)
1044            }
1045            Expression::AggregationComprehension {
1046                function,
1047                variable,
1048                collection,
1049                window,
1050                predicate,
1051                projection,
1052                target_unit,
1053            } => {
1054                let v = Expression::evaluate_aggregation_comprehension(
1055                    function,
1056                    variable,
1057                    collection,
1058                    window,
1059                    predicate,
1060                    projection,
1061                    target_unit.as_deref(),
1062                    graph,
1063                )?;
1064                Ok(v)
1065            }
1066            Expression::Cast {
1067                operand,
1068                target_type,
1069            } => {
1070                let val = Self::get_runtime_value(operand, graph)?;
1071                let (value_dec, source_unit) = Self::parse_numeric_with_unit_value(&val)
1072                    .map_err(|e| format!("Invalid cast operand: {}", e))?
1073                    .ok_or_else(|| {
1074                        format!("Cannot cast non-numeric value {} to {}", val, target_type)
1075                    })?;
1076
1077                let registry = get_default_registry();
1078                let registry = registry
1079                    .read()
1080                    .map_err(|e| format!("Failed to lock unit registry: {}", e))?;
1081                let target_unit = registry
1082                    .get_unit(target_type)
1083                    .map_err(|e| format!("Unknown target unit '{}': {}", target_type, e))?;
1084
1085                let converted_value = if let Some(from_unit_symbol) = source_unit {
1086                    let from_unit = registry
1087                        .get_unit(&from_unit_symbol)
1088                        .map_err(|e| format!("Unknown unit '{}': {}", from_unit_symbol, e))?;
1089                    if from_unit.dimension() != target_unit.dimension() {
1090                        return Err(format!(
1091                            "Cannot cast from '{}' ({:?}) to '{}' ({:?})",
1092                            from_unit_symbol,
1093                            from_unit.dimension(),
1094                            target_type,
1095                            target_unit.dimension()
1096                        ));
1097                    }
1098                    registry
1099                        .convert(value_dec, from_unit, target_unit)
1100                        .map_err(|e| format!("Unit conversion failed: {}", e))?
1101                } else {
1102                    value_dec
1103                };
1104
1105                Ok(serde_json::json!({
1106                    "__quantity_value": converted_value.to_string(),
1107                    "__quantity_unit": target_type
1108                }))
1109            }
1110            Expression::QuantityLiteral { value, unit } => Ok(
1111                serde_json::json!({"__quantity_value": value.to_string(), "__quantity_unit": unit}),
1112            ),
1113            Expression::TimeLiteral(timestamp) => Ok(serde_json::json!(timestamp)),
1114            Expression::IntervalLiteral { start, end } => {
1115                Ok(serde_json::json!({"__interval_start": start, "__interval_end": end}))
1116            }
1117            _ => Err(
1118                "Expected a runtime-resolvable expression (literal, member access, or aggregation)"
1119                    .to_string(),
1120            ),
1121        }
1122    }
1123}
1124
1125impl PolicyModality {
1126    pub fn to_severity(&self) -> Severity {
1127        match self {
1128            Self::Obligation => Severity::Error,
1129            Self::Prohibition => Severity::Error,
1130            Self::Permission => Severity::Info,
1131        }
1132    }
1133}
1134
1135impl EvaluationResult {
1136    pub fn has_errors(&self) -> bool {
1137        self.violations
1138            .iter()
1139            .any(|v| v.severity == Severity::Error)
1140    }
1141
1142    pub fn error_count(&self) -> usize {
1143        self.violations
1144            .iter()
1145            .filter(|v| v.severity == Severity::Error)
1146            .count()
1147    }
1148}