Skip to main content

trit_policy/
lib.rs

1//! 三值决策表的构建期验证。
2//!
3//! 策略文件是 JSON:变量分三值/二值,`definitions` 给出派生信号,
4//! `assertions` 里每条表达式必须对所有合法赋值都为真。
5//!
6//! ```json
7//! {
8//!   "algebra": "kleene",
9//!   "binary": ["infeasible", "contended"],
10//!   "definitions": { "reject": "infeasible | (hopeless & contended)" },
11//!   "assertions": [
12//!     { "name": "硬约束", "expr": "infeasible => reject" },
13//!     { "name": "空槽不拒", "expr": "(!contended & !infeasible) => !reject" }
14//!   ]
15//! }
16//! ```
17//!
18//! 有 `assumptions` 时,断言只在**前提确定为 True** 的赋值上检查:
19//! 查询是“前提为 True 且断言不为 True”是否可满足,而不是三值 `premise ⇒ assertion` 恒真
20//! (后者会把 `a=X` 这类不满足前提的赋值也算进去)。
21
22#![forbid(unsafe_code)]
23
24use std::collections::{BTreeMap, HashMap, HashSet};
25use std::path::Path;
26
27use serde::Deserialize;
28
29pub use trit_bdd::{BddLimits, VarOrder};
30
31use trit_bdd::{
32    BddError, BddRequest, order_variables, premise_counterexample_bdd_with, satisfiable_bdd_with,
33};
34use trit_core::algebra::Algebra;
35use trit_core::ast::Expr;
36use trit_core::limits::{LimitError, Limits, check_expr};
37use trit_core::parser::Parser;
38use trit_core::value::Ternary;
39use trit_eval::Assignment;
40use trit_sat::{SymbolicError, premise_counterexample, satisfiable};
41
42/// 验证引擎。
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum Engine {
45    /// SAT;失败时返回完整模型(不做最小化)。
46    Sat,
47    /// canonical BDD。
48    Bdd,
49}
50
51impl Engine {
52    /// 解析 `sat` / `bdd`。
53    pub fn parse(name: &str) -> Option<Self> {
54        match name.to_ascii_lowercase().as_str() {
55            "sat" => Some(Self::Sat),
56            "bdd" => Some(Self::Bdd),
57            _ => None,
58        }
59    }
60
61    /// 稳定名称。
62    pub const fn as_str(self) -> &'static str {
63        match self {
64            Self::Sat => "sat",
65            Self::Bdd => "bdd",
66        }
67    }
68}
69
70/// 策略文件。
71#[derive(Debug, Clone, Deserialize)]
72#[serde(deny_unknown_fields)]
73pub struct Policy {
74    /// 语义,默认 `kleene`。
75    #[serde(default = "default_algebra")]
76    pub algebra: String,
77    /// 已知二值的变量。
78    #[serde(default)]
79    pub binary: Vec<String>,
80    /// 前提约束:必须可满足;断言只在满足前提的赋值上检查。
81    #[serde(default)]
82    pub assumptions: Vec<String>,
83    /// 派生信号:名字 -> 表达式。
84    #[serde(default)]
85    pub definitions: BTreeMap<String, String>,
86    /// 必须恒真的断言。
87    pub assertions: Vec<Assertion>,
88}
89
90/// 一条断言。
91#[derive(Debug, Clone, Deserialize)]
92#[serde(deny_unknown_fields)]
93pub struct Assertion {
94    /// 断言名。
95    pub name: String,
96    /// 表达式。
97    pub expr: String,
98}
99
100fn default_algebra() -> String {
101    "kleene".to_string()
102}
103
104/// 单条断言的结果。
105#[derive(Debug, Clone, PartialEq, Eq)]
106pub struct AssertionResult {
107    /// 断言名。
108    pub name: String,
109    /// 是否恒真。
110    pub holds: bool,
111    /// 失败时的反例(两个引擎都返回完整赋值模型,不做最小化)。
112    pub counterexample: Option<Assignment>,
113    /// BDD 路径的差集节点数。
114    pub nodes: Option<usize>,
115}
116
117/// 策略错误。
118#[derive(Debug, thiserror::Error)]
119pub enum PolicyError {
120    /// 读文件失败。
121    #[error("读取策略失败: {0}")]
122    Io(String),
123    /// JSON 无效。
124    #[error("策略 JSON 无效: {0}")]
125    Json(String),
126    /// 未知语义。
127    #[error("未知语义: {0}(可选 kleene / lukasiewicz / bochvar)")]
128    Algebra(String),
129    /// 表达式解析失败。
130    #[error("{what} 解析失败: {error}")]
131    Parse {
132        /// 出错的表达式名。
133        what: String,
134        /// 解析器错误(含行列位置)。
135        error: trit_core::parser::ParseError,
136    },
137    /// 定义循环。
138    #[error("定义循环: {0}")]
139    Cycle(String),
140    /// 前提不一致。
141    #[error("前提不一致:没有合法赋值能让所有 assumptions 为 True")]
142    InconsistentAssumptions,
143    /// 策略没有任何断言。
144    #[error("策略没有断言:拒绝作为门禁运行")]
145    NoAssertions,
146    /// 名称不合法。
147    #[error("{kind} 名称 {name:?} 不合法:{reason}")]
148    InvalidName {
149        /// 名称类别(`definition` / `binary`)。
150        kind: &'static str,
151        /// 原始名称。
152        name: String,
153        /// 原因。
154        reason: String,
155    },
156    /// 二值变量指向了派生定义。
157    #[error("binary 里的 {0:?} 是派生定义;binary 只允许原始变量")]
158    BinaryIsDefinition(String),
159    /// 二值变量重复。
160    #[error("binary 列表重复:{0}")]
161    DuplicateBinary(String),
162    /// 定义展开超出节点预算。
163    #[error("定义展开超过节点预算 {limit}")]
164    ExpandBudget {
165        /// 上限。
166        limit: usize,
167    },
168    /// 定义引用链过深。
169    #[error("定义引用深度超过 {limit}")]
170    DefinitionDepth {
171        /// 上限。
172        limit: usize,
173    },
174    /// SAT 引擎错误。
175    #[error("SAT 错误: {0}")]
176    Sat(#[from] SymbolicError),
177    /// BDD 引擎错误。
178    #[error("BDD 错误: {0}")]
179    Bdd(#[from] BddError),
180    /// 表达式超出资源预算。
181    #[error("表达式超出预算: {0}")]
182    Limit(#[from] LimitError),
183}
184
185impl From<serde_json::Error> for PolicyError {
186    fn from(value: serde_json::Error) -> Self {
187        Self::Json(value.to_string())
188    }
189}
190
191impl Policy {
192    /// 从文件读取。
193    pub fn load(path: impl AsRef<Path>) -> Result<Self, PolicyError> {
194        let text = std::fs::read_to_string(path.as_ref())
195            .map_err(|e| PolicyError::Io(format!("{}: {e}", path.as_ref().display())))?;
196        Self::from_json(&text)
197    }
198
199    /// 从 JSON 文本解析。
200    pub fn from_json(text: &str) -> Result<Self, PolicyError> {
201        Ok(serde_json::from_str(text)?)
202    }
203
204    /// 逐条验证断言;任何一条失败都返回 `holds=false` 的结果,不提前中止。
205    pub fn verify(&self, engine: Engine) -> Result<Vec<AssertionResult>, PolicyError> {
206        self.verify_with_bdd_config(engine, None, BddLimits::default())
207    }
208
209    /// 同 [`Policy::verify`],但可指定 BDD 的静态变量顺序(仅 `Engine::Bdd` 生效)。
210    pub fn verify_with_bdd_order(
211        &self,
212        engine: Engine,
213        bdd_order: Option<VarOrder>,
214    ) -> Result<Vec<AssertionResult>, PolicyError> {
215        self.verify_with_bdd_config(engine, bdd_order, BddLimits::default())
216    }
217
218    /// 同 [`Policy::verify`],可同时指定变量顺序与节点预算。
219    pub fn verify_with_bdd_config(
220        &self,
221        engine: Engine,
222        bdd_order: Option<VarOrder>,
223        bdd_limits: BddLimits,
224    ) -> Result<Vec<AssertionResult>, PolicyError> {
225        self.verify_with_bdd_config_and_jobs(engine, bdd_order, bdd_limits, 1)
226    }
227
228    /// 同 [`Policy::verify_with_bdd_config`],但按 `jobs` 并行检查断言。
229    ///
230    /// `jobs = 0` 表示自动取可用并行度,`1` 表示串行;结果顺序与串行一致。
231    /// 前提可满足性只检查一次,然后各断言独立求解。
232    pub fn verify_with_bdd_config_and_jobs(
233        &self,
234        engine: Engine,
235        bdd_order: Option<VarOrder>,
236        bdd_limits: BddLimits,
237        jobs: usize,
238    ) -> Result<Vec<AssertionResult>, PolicyError> {
239        if self.assertions.is_empty() {
240            return Err(PolicyError::NoAssertions);
241        }
242        let alg = Algebra::parse(&self.algebra)
243            .ok_or_else(|| PolicyError::Algebra(self.algebra.clone()))?;
244        let binary = validate_binary(&self.binary, &self.definitions)?;
245        let mut definitions = BTreeMap::new();
246        for (name, src) in &self.definitions {
247            Expr::try_var(name.clone()).map_err(|e| PolicyError::InvalidName {
248                kind: "definition",
249                name: name.clone(),
250                reason: e.to_string(),
251            })?;
252            definitions.insert(name.clone(), parse_expr(name, src)?);
253        }
254        let limits = Limits {
255            max_vars: usize::MAX,
256            ..Limits::default()
257        };
258        // 先检查完整定义图(含未被引用的循环),再开始任何展开分配。
259        validate_definition_graph(&definitions, limits)?;
260        let truth = Expr::Const(Ternary::True);
261        let mut assumptions = Vec::with_capacity(self.assumptions.len());
262        for (i, src) in self.assumptions.iter().enumerate() {
263            let raw = parse_expr(&format!("assumption[{i}]"), src)?;
264            assumptions.push(expand_with_limits(&raw, &definitions, limits)?);
265        }
266        let premise = assumptions.into_iter().fold(truth.clone(), Expr::and);
267        check_expr(&premise, limits)?;
268        let satisfiable_premise = match engine {
269            Engine::Sat => satisfiable(&premise, alg, &binary)?,
270            Engine::Bdd => {
271                let vars = bdd_order.map(|order| order_variables(&premise, &premise, order));
272                let request = BddRequest {
273                    order: vars.as_deref(),
274                    limits: bdd_limits,
275                };
276                satisfiable_bdd_with(&premise, alg, &binary, &request)?
277            }
278        };
279        if satisfiable_premise.is_none() {
280            return Err(PolicyError::InconsistentAssumptions);
281        }
282
283        let context = VerifyContext {
284            engine,
285            bdd_order,
286            bdd_limits,
287            definitions: &definitions,
288            premise: &premise,
289            alg,
290            binary: &binary,
291            limits,
292        };
293        trit_core::parallel::map(&self.assertions, jobs, |assertion| context.check(assertion))
294    }
295}
296
297struct VerifyContext<'a> {
298    engine: Engine,
299    bdd_order: Option<VarOrder>,
300    bdd_limits: BddLimits,
301    definitions: &'a BTreeMap<String, Expr>,
302    premise: &'a Expr,
303    alg: Algebra,
304    binary: &'a HashSet<String>,
305    limits: Limits,
306}
307
308impl VerifyContext<'_> {
309    fn check(&self, assertion: &Assertion) -> Result<AssertionResult, PolicyError> {
310        let raw = parse_expr(&assertion.name, &assertion.expr)?;
311        let expr = expand_with_limits(&raw, self.definitions, self.limits)?;
312        check_expr(&expr, self.limits)?;
313        match self.engine {
314            Engine::Sat => {
315                let witness = premise_counterexample(self.premise, &expr, self.alg, self.binary)?;
316                Ok(AssertionResult {
317                    name: assertion.name.clone(),
318                    holds: witness.is_none(),
319                    counterexample: witness,
320                    nodes: None,
321                })
322            }
323            Engine::Bdd => {
324                let vars = self
325                    .bdd_order
326                    .map(|order| order_variables(self.premise, &expr, order));
327                let request = BddRequest {
328                    order: vars.as_deref(),
329                    limits: self.bdd_limits,
330                };
331                let check = premise_counterexample_bdd_with(
332                    self.premise,
333                    &expr,
334                    self.alg,
335                    self.binary,
336                    &request,
337                )?;
338                Ok(AssertionResult {
339                    name: assertion.name.clone(),
340                    holds: check.counterexample.is_none(),
341                    counterexample: check.counterexample,
342                    nodes: Some(check.nodes),
343                })
344            }
345        }
346    }
347}
348
349fn parse_expr(what: &str, src: &str) -> Result<Expr, PolicyError> {
350    Parser::parse(src).map_err(|error| PolicyError::Parse {
351        what: what.to_string(),
352        error,
353    })
354}
355
356/// 校验 `binary` 列表:只允许原始变量,且不能与派生定义重名。
357fn validate_binary(
358    list: &[String],
359    definitions: &BTreeMap<String, String>,
360) -> Result<HashSet<String>, PolicyError> {
361    let mut set = HashSet::new();
362    for name in list {
363        let reason = match Parser::parse(name) {
364            Ok(Expr::Var(parsed)) if parsed == *name => None,
365            Ok(Expr::Const(_)) => Some("是保留常量 token".to_string()),
366            Ok(_) => Some("不是单个标识符".to_string()),
367            Err(e) => Some(e.to_string()),
368        };
369        if let Some(reason) = reason {
370            return Err(PolicyError::InvalidName {
371                kind: "binary",
372                name: name.clone(),
373                reason,
374            });
375        }
376        if definitions.contains_key(name) {
377            return Err(PolicyError::BinaryIsDefinition(name.clone()));
378        }
379        if !set.insert(name.clone()) {
380            return Err(PolicyError::DuplicateBinary(name.clone()));
381        }
382    }
383    Ok(set)
384}
385
386/// 定义表达式中指向其它定义的自由变量。
387fn definition_deps<'a>(definitions: &'a BTreeMap<String, Expr>, expr: &Expr) -> Vec<&'a str> {
388    expr.variables()
389        .into_iter()
390        .filter_map(|name| {
391            definitions
392                .get_key_value(&name)
393                .map(|(key, _)| key.as_str())
394        })
395        .collect()
396}
397
398/// 迭代 DFS 检查完整定义图(含未被引用的定义)是否有环、引用链是否过深。
399fn validate_definition_graph(
400    definitions: &BTreeMap<String, Expr>,
401    limits: Limits,
402) -> Result<(), PolicyError> {
403    #[derive(Clone, Copy, PartialEq, Eq)]
404    enum State {
405        New,
406        Visiting,
407        Done,
408    }
409
410    struct Frame<'a> {
411        name: &'a str,
412        children: Vec<&'a str>,
413        next: usize,
414    }
415
416    let mut state: HashMap<&str, State> = definitions
417        .keys()
418        .map(|name| (name.as_str(), State::New))
419        .collect();
420    for root in definitions.keys() {
421        let root = root.as_str();
422        if state.get(root).copied() != Some(State::New) {
423            continue;
424        }
425        state.insert(root, State::Visiting);
426        let mut stack = vec![Frame {
427            name: root,
428            children: definition_deps(definitions, &definitions[root]),
429            next: 0,
430        }];
431        while !stack.is_empty() {
432            let top = stack.len() - 1;
433            if stack[top].next < stack[top].children.len() {
434                let child = stack[top].children[stack[top].next];
435                stack[top].next += 1;
436                match state.get(child).copied().unwrap_or(State::New) {
437                    State::New => {
438                        if stack.len() + 1 > limits.max_depth {
439                            return Err(PolicyError::DefinitionDepth {
440                                limit: limits.max_depth,
441                            });
442                        }
443                        state.insert(child, State::Visiting);
444                        stack.push(Frame {
445                            name: child,
446                            children: definition_deps(definitions, &definitions[child]),
447                            next: 0,
448                        });
449                    }
450                    State::Visiting => {
451                        let mut cycle: Vec<&str> = stack.iter().map(|frame| frame.name).collect();
452                        cycle.push(child);
453                        return Err(PolicyError::Cycle(cycle.join(" -> ")));
454                    }
455                    State::Done => {}
456                }
457            } else {
458                let name = stack[top].name;
459                stack.pop();
460                state.insert(name, State::Done);
461            }
462        }
463    }
464    Ok(())
465}
466
467/// 带节点/深度预算的展开器。
468struct Expander<'a> {
469    definitions: &'a BTreeMap<String, Expr>,
470    stack: Vec<String>,
471    nodes_left: usize,
472    node_limit: usize,
473    max_depth: usize,
474}
475
476impl<'a> Expander<'a> {
477    fn new(definitions: &'a BTreeMap<String, Expr>, limits: Limits) -> Self {
478        Self {
479            definitions,
480            stack: Vec::new(),
481            nodes_left: limits.max_nodes,
482            node_limit: limits.max_nodes,
483            max_depth: limits.max_depth,
484        }
485    }
486
487    fn take_node(&mut self) -> Result<(), PolicyError> {
488        if self.nodes_left == 0 {
489            return Err(PolicyError::ExpandBudget {
490                limit: self.node_limit,
491            });
492        }
493        self.nodes_left -= 1;
494        Ok(())
495    }
496
497    fn check_depth(&self, depth: usize) -> Result<(), PolicyError> {
498        if depth > self.max_depth {
499            return Err(PolicyError::Limit(LimitError::Depth {
500                limit: self.max_depth,
501            }));
502        }
503        Ok(())
504    }
505
506    fn join(
507        &mut self,
508        left: (Expr, usize),
509        right: (Expr, usize),
510        f: impl FnOnce(Expr, Expr) -> Expr,
511    ) -> Result<(Expr, usize), PolicyError> {
512        let depth = 1 + left.1.max(right.1);
513        self.check_depth(depth)?;
514        self.take_node()?;
515        Ok((f(left.0, right.0), depth))
516    }
517
518    fn expand(&mut self, expr: &Expr) -> Result<(Expr, usize), PolicyError> {
519        match expr {
520            Expr::Const(_) => {
521                self.take_node()?;
522                Ok((expr.clone(), 1))
523            }
524            Expr::Var(name) => {
525                let Some(definition) = self.definitions.get(name) else {
526                    self.take_node()?;
527                    return Ok((expr.clone(), 1));
528                };
529                if self.stack.len() >= self.max_depth {
530                    return Err(PolicyError::DefinitionDepth {
531                        limit: self.max_depth,
532                    });
533                }
534                if self.stack.iter().any(|n| n == name) {
535                    let mut cycle = self.stack.clone();
536                    cycle.push(name.clone());
537                    return Err(PolicyError::Cycle(cycle.join(" -> ")));
538                }
539                self.stack.push(name.clone());
540                let expanded = self.expand(definition);
541                self.stack.pop();
542                expanded
543            }
544            Expr::Not(inner) => {
545                let (inner, depth) = self.expand(inner)?;
546                let depth = 1 + depth;
547                self.check_depth(depth)?;
548                self.take_node()?;
549                Ok((Expr::logical_not(inner), depth))
550            }
551            Expr::And(a, b) => {
552                let a = self.expand(a)?;
553                let b = self.expand(b)?;
554                self.join(a, b, Expr::and)
555            }
556            Expr::Or(a, b) => {
557                let a = self.expand(a)?;
558                let b = self.expand(b)?;
559                self.join(a, b, Expr::or)
560            }
561            Expr::Xor(a, b) => {
562                let a = self.expand(a)?;
563                let b = self.expand(b)?;
564                self.join(a, b, Expr::xor)
565            }
566            Expr::Implies(a, b) => {
567                let a = self.expand(a)?;
568                let b = self.expand(b)?;
569                self.join(a, b, Expr::implies)
570            }
571            Expr::Equiv(a, b) => {
572                let a = self.expand(a)?;
573                let b = self.expand(b)?;
574                self.join(a, b, Expr::equiv)
575            }
576        }
577    }
578}
579
580fn expand_with_limits(
581    expr: &Expr,
582    definitions: &BTreeMap<String, Expr>,
583    limits: Limits,
584) -> Result<Expr, PolicyError> {
585    let mut expander = Expander::new(definitions, limits);
586    Ok(expander.expand(expr)?.0)
587}
588
589#[cfg(test)]
590mod tests {
591    use super::*;
592
593    fn defs(pairs: &[(&str, &str)]) -> BTreeMap<String, Expr> {
594        pairs
595            .iter()
596            .map(|(name, src)| (name.to_string(), Parser::parse(src).unwrap()))
597            .collect()
598    }
599
600    #[test]
601    fn expand_budget_is_exact_and_fails_before_growth() {
602        let definitions = defs(&[("d0", "a"), ("d1", "d0 & d0")]);
603        let expr = Parser::parse("d1").unwrap();
604        let exact = Limits {
605            max_nodes: 3,
606            ..Limits::default()
607        };
608        assert!(expand_with_limits(&expr, &definitions, exact).is_ok());
609        let tight = Limits {
610            max_nodes: 2,
611            ..Limits::default()
612        };
613        assert!(matches!(
614            expand_with_limits(&expr, &definitions, tight),
615            Err(PolicyError::ExpandBudget { limit: 2 })
616        ));
617    }
618
619    #[test]
620    fn expand_depth_is_enforced() {
621        let definitions = defs(&[("d0", "a"), ("d1", "d0"), ("d2", "d1"), ("d3", "d2")]);
622        let expr = Parser::parse("d3").unwrap();
623        let limits = Limits {
624            max_depth: 3,
625            ..Limits::default()
626        };
627        assert!(matches!(
628            expand_with_limits(&expr, &definitions, limits),
629            Err(PolicyError::DefinitionDepth { limit: 3 })
630        ));
631    }
632}
633
634#[test]
635fn parallel_verify_matches_serial() {
636    let binary = (0..64).map(|index| format!("v{index}")).collect();
637    let assertions = (0..64)
638        .map(|index| Assertion {
639            name: format!("tautology{index}"),
640            expr: format!("v{index} | !v{index}"),
641        })
642        .collect();
643    let policy = Policy {
644        algebra: "kleene".to_string(),
645        binary,
646        assumptions: Vec::new(),
647        definitions: BTreeMap::new(),
648        assertions,
649    };
650    let serial = policy
651        .verify_with_bdd_config_and_jobs(Engine::Sat, None, BddLimits::default(), 1)
652        .unwrap();
653    let parallel = policy
654        .verify_with_bdd_config_and_jobs(Engine::Sat, None, BddLimits::default(), 4)
655        .unwrap();
656    assert_eq!(serial, parallel);
657    assert!(serial.iter().all(|result| result.holds));
658}