logicaffeine_language/analysis/
policy.rs

1//! Security Policy Registry.
2//!
3//! Stores predicate and capability definitions parsed from `## Policy` blocks.
4//! These are used to generate security methods on structs and enforce them
5//! with the `Check` statement.
6
7use std::collections::HashMap;
8use logicaffeine_base::Symbol;
9
10/// Condition in a policy definition.
11/// Represents the predicate logic for security rules.
12#[derive(Debug, Clone)]
13pub enum PolicyCondition {
14    /// Field comparison: `the user's role equals "admin"`
15    FieldEquals {
16        field: Symbol,
17        value: Symbol,
18        /// Whether the value came from a string literal (needs quotes in codegen)
19        is_string_literal: bool,
20    },
21    /// Boolean field: `the user's verified equals true`
22    FieldBool {
23        field: Symbol,
24        value: bool,
25    },
26    /// Predicate call: `the user is admin`
27    Predicate {
28        subject: Symbol,
29        predicate: Symbol,
30    },
31    /// Object field comparison: `the user equals the document's owner`
32    ObjectFieldEquals {
33        subject: Symbol,
34        object: Symbol,
35        field: Symbol,
36    },
37    /// Logical OR: `A OR B`
38    Or(Box<PolicyCondition>, Box<PolicyCondition>),
39    /// Logical AND: `A AND B`
40    And(Box<PolicyCondition>, Box<PolicyCondition>),
41}
42
43/// A predicate definition: `A User is admin if the user's role equals "admin".`
44#[derive(Debug, Clone)]
45pub struct PredicateDef {
46    /// The type this predicate applies to (e.g., "User")
47    pub subject_type: Symbol,
48    /// The predicate name (e.g., "admin")
49    pub predicate_name: Symbol,
50    /// The condition that must be true
51    pub condition: PolicyCondition,
52}
53
54/// A capability definition: `A User can publish the Document if...`
55#[derive(Debug, Clone)]
56pub struct CapabilityDef {
57    /// The type that has this capability (e.g., "User")
58    pub subject_type: Symbol,
59    /// The action name (e.g., "publish")
60    pub action: Symbol,
61    /// The object type the action applies to (e.g., "Document")
62    pub object_type: Symbol,
63    /// The condition that must be true
64    pub condition: PolicyCondition,
65}
66
67/// Registry for security policies defined in `## Policy` blocks.
68#[derive(Debug, Default, Clone)]
69pub struct PolicyRegistry {
70    /// Predicates indexed by subject type
71    predicates: HashMap<Symbol, Vec<PredicateDef>>,
72    /// Capabilities indexed by subject type
73    capabilities: HashMap<Symbol, Vec<CapabilityDef>>,
74}
75
76impl PolicyRegistry {
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Register a predicate definition
82    pub fn register_predicate(&mut self, def: PredicateDef) {
83        self.predicates
84            .entry(def.subject_type)
85            .or_insert_with(Vec::new)
86            .push(def);
87    }
88
89    /// Register a capability definition
90    pub fn register_capability(&mut self, def: CapabilityDef) {
91        self.capabilities
92            .entry(def.subject_type)
93            .or_insert_with(Vec::new)
94            .push(def);
95    }
96
97    /// Get predicates for a type
98    pub fn get_predicates(&self, subject_type: Symbol) -> Option<&[PredicateDef]> {
99        self.predicates.get(&subject_type).map(|v| v.as_slice())
100    }
101
102    /// Get capabilities for a type
103    pub fn get_capabilities(&self, subject_type: Symbol) -> Option<&[CapabilityDef]> {
104        self.capabilities.get(&subject_type).map(|v| v.as_slice())
105    }
106
107    /// Check if a type has any predicates
108    pub fn has_predicates(&self, subject_type: Symbol) -> bool {
109        self.predicates.contains_key(&subject_type)
110    }
111
112    /// Check if a type has any capabilities
113    pub fn has_capabilities(&self, subject_type: Symbol) -> bool {
114        self.capabilities.contains_key(&subject_type)
115    }
116
117    /// Iterate over all types with predicates (for codegen)
118    pub fn iter_predicates(&self) -> impl Iterator<Item = (&Symbol, &Vec<PredicateDef>)> {
119        self.predicates.iter()
120    }
121
122    /// Iterate over all types with capabilities (for codegen)
123    pub fn iter_capabilities(&self) -> impl Iterator<Item = (&Symbol, &Vec<CapabilityDef>)> {
124        self.capabilities.iter()
125    }
126
127    /// Check if registry has any policies
128    pub fn is_empty(&self) -> bool {
129        self.predicates.is_empty() && self.capabilities.is_empty()
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use logicaffeine_base::Interner;
137
138    #[test]
139    fn registry_stores_predicates() {
140        let mut interner = Interner::new();
141        let mut registry = PolicyRegistry::new();
142
143        let user = interner.intern("User");
144        let admin = interner.intern("admin");
145        let role = interner.intern("role");
146        let admin_val = interner.intern("admin");
147
148        let def = PredicateDef {
149            subject_type: user,
150            predicate_name: admin,
151            condition: PolicyCondition::FieldEquals {
152                field: role,
153                value: admin_val,
154                is_string_literal: true,
155            },
156        };
157
158        registry.register_predicate(def);
159
160        assert!(registry.has_predicates(user));
161        let preds = registry.get_predicates(user).unwrap();
162        assert_eq!(preds.len(), 1);
163        assert_eq!(preds[0].predicate_name, admin);
164    }
165
166    #[test]
167    fn registry_stores_capabilities() {
168        let mut interner = Interner::new();
169        let mut registry = PolicyRegistry::new();
170
171        let user = interner.intern("User");
172        let doc = interner.intern("Document");
173        let publish = interner.intern("publish");
174        let admin = interner.intern("admin");
175        let user_var = interner.intern("user");
176
177        let def = CapabilityDef {
178            subject_type: user,
179            action: publish,
180            object_type: doc,
181            condition: PolicyCondition::Predicate {
182                subject: user_var,
183                predicate: admin,
184            },
185        };
186
187        registry.register_capability(def);
188
189        assert!(registry.has_capabilities(user));
190        let caps = registry.get_capabilities(user).unwrap();
191        assert_eq!(caps.len(), 1);
192        assert_eq!(caps[0].action, publish);
193        assert_eq!(caps[0].object_type, doc);
194    }
195}