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}