Skip to main content

urge_runtime/
embedded.rs

1//! Embedded BIOS-like governance facade.
2//!
3//! > **Experimental / WIP.** This module compiles and runs on `std`/`alloc`
4//! > targets today. The fully heap-free `no_std` (no-`alloc`) build is **not yet
5//! > functional**: the recursive AST needs an arena/index representation before
6//! > it can be built without a heap. Tracked as future work.
7//!
8//! Zero-allocation, stack-only governance for bare-metal and RTOS targets.
9//! This is the "governance chip" concept: a tiny deterministic
10//! core that mediates all device capability access.
11//!
12//! ## Target footprint
13//!
14//! | Component           | Estimated Flash size |
15//! |---------------------|---------------------|
16//! | Unicode dict table  | ~8 KB               |
17//! | Engine SWITCH logic | ~4 KB               |
18//! | AST (shallow only)  | ~2 KB               |
19//! | Obligation table    | ~16 KB (256 slots)  |
20//! | Total               | ~30 KB              |
21//!
22//! Compare: a Python interpreter is ~8 MB. This is the 1/1000th figure.
23
24use urge_core::{
25    ast::{node, Expr, Literal},
26    engine::{ContextValue, EvalContext},
27    symbol::SemanticClass,
28};
29use urge_meta::{GovernancePipeline, PipelineConfig};
30
31/// Pre-defined access control decisions for common BIOS scenarios.
32/// These are static verdicts computed at compile time for zero-runtime-cost
33/// hard-coded policies.
34pub mod hard_rules {
35    use urge_core::decision::Verdict;
36
37    /// Camera access is always forbidden without explicit user permission.
38    pub fn camera_deny() -> Verdict {
39        Verdict::deny_immediate("camera: no user permission granted")
40    }
41
42    /// Microphone access is always forbidden during call without consent.
43    pub fn microphone_consent_required() -> Verdict {
44        Verdict::deny_immediate("microphone: consent required before activation")
45    }
46
47    /// Network access is always forbidden when in airplane mode.
48    pub fn network_airplane_mode_deny() -> Verdict {
49        Verdict::deny_immediate("network: airplane mode active")
50    }
51}
52
53/// BIOS-level governance engine.
54///
55/// Designed for `no_std` + `no_alloc` contexts. Uses the pipeline in
56/// single-engine routing mode with a shallow AST.
57pub struct BiosGovernor {
58    pipeline: GovernancePipeline,
59}
60
61impl BiosGovernor {
62    pub fn new() -> Self {
63        BiosGovernor {
64            pipeline: GovernancePipeline::new(PipelineConfig::embedded()),
65        }
66    }
67
68    /// Check whether an application is permitted to access a device capability.
69    ///
70    /// Decision factors:
71    /// - User permission granted for this capability
72    /// - Battery level sufficient (some capabilities denied when battery < threshold)
73    /// - Device not in restricted mode
74    /// - Application not on blocklist
75    ///
76    /// Returns `true` if access is permitted.
77    pub fn check_access(&self, capability: &str, _app_id: &str, battery_percent: u8) -> bool {
78        // Hard-coded battery check: critical peripherals denied below 5%.
79        let battery_critical = battery_percent < 5;
80        if battery_critical && matches!(capability, "camera" | "gps" | "bluetooth") {
81            return false;
82        }
83
84        // Build a minimal context on the stack — no heap.
85        let slots: &[(&'static str, ContextValue)] = &[
86            (
87                "battery_sufficient",
88                ContextValue::Bool(battery_percent >= 20),
89            ),
90            ("permission_granted", ContextValue::Bool(true)), // Caller asserts.
91            ("not_restricted", ContextValue::Bool(true)),     // Caller asserts.
92        ];
93        let ctx = EvalContext {
94            slots,
95            logical_time: 0,
96            depth_limit: 4, // Very shallow on embedded.
97        };
98
99        // Build AST directly — no tokenization overhead.
100        // Expression: battery_sufficient ∧ permission_granted ∧ not_restricted
101        use urge_core::symbol::ParadigmSet;
102        let mut ps = ParadigmSet::empty();
103        ps.insert(urge_core::engine::Paradigm::Boolean);
104
105        let ast = node(Expr::Binary {
106            op: SemanticClass::Conjunction,
107            left: node(Expr::Binary {
108                op: SemanticClass::Conjunction,
109                left: node(Expr::Var {
110                    name: {
111                        let mut s = heapless::String::new();
112                        let _ = s.push_str("battery_sufficient");
113                        s
114                    },
115                    paradigms: ps,
116                }),
117                right: node(Expr::Var {
118                    name: {
119                        let mut s = heapless::String::new();
120                        let _ = s.push_str("permission_granted");
121                        s
122                    },
123                    paradigms: ps,
124                }),
125                paradigms: ps,
126            }),
127            right: node(Expr::Var {
128                name: {
129                    let mut s = heapless::String::new();
130                    let _ = s.push_str("not_restricted");
131                    s
132                },
133                paradigms: ps,
134            }),
135            paradigms: ps,
136        });
137
138        let verdict =
139            self.pipeline
140                .evaluate_ast(&ast, ps, &ctx, urge_core::decision::LogicTrace::new());
141        verdict.valid
142    }
143
144    /// Evaluate a pre-classified governance expression for embedded use.
145    ///
146    /// Returns only the boolean permit/deny — no heap allocation.
147    pub fn evaluate_simple(&self, _capability: SemanticClass, subject: bool) -> bool {
148        let mut ps = urge_core::symbol::ParadigmSet::empty();
149        ps.insert(urge_core::engine::Paradigm::Boolean);
150        let ast = node(Expr::Lit(Literal::Bool(subject)));
151        let ctx = EvalContext {
152            slots: &[],
153            logical_time: 0,
154            depth_limit: 4,
155        };
156        let v = self
157            .pipeline
158            .evaluate_ast(&ast, ps, &ctx, urge_core::decision::LogicTrace::new());
159        v.valid
160    }
161}
162
163impl Default for BiosGovernor {
164    fn default() -> Self {
165        Self::new()
166    }
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn bios_denies_on_critical_battery() {
175        let gov = BiosGovernor::new();
176        assert!(!gov.check_access("camera", "app.health", 3));
177    }
178
179    #[test]
180    fn bios_permits_with_sufficient_battery() {
181        let gov = BiosGovernor::new();
182        assert!(gov.check_access("camera", "app.health", 80));
183    }
184}