Skip to main content

evorule_cli/commands/
validate.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright (C) 2026 EvoRule Project
3// This file is part of EvoRule, licensed under GNU Affero General Public License v3 or later.
4//! `evorule validate` —— 校验 JSON 规则文件(core_eval 元指令白名单)
5//!
6//! # 设计调整(相对于原方案 §2.6)
7//! 原方案拟用 tier1 `RuleValidator` 替代手写白名单。但实测发现 `RuleValidator`
8//! 验证的是 **instruction** 类型(increment/decrement/set/noop/sequence/conditional/
9//! while_loop/push),而 CLI validate 验证的是 **core_eval 规则**类型(branch/set/
10//! push/io_request/noop 等元指令)。两者类型不匹配,`RuleValidator` 会把合法的
11//! `branch` 规则误报为 "Unknown instruction type"。
12//!
13//! 故改用 core_eval 元指令白名单。此白名单不含 G8 禁止词
14//!(conditional/while_loop/sequence),故 build.rs 无需任何豁免(零豁免原则保持)。
15//!
16//! # 白名单来源
17//! evorule-tcb/src/executor.rs 的 `execute_meta_instruction` dispatch 处理的
18//! 元指令类型:branch / set / push / io_request / collect / merge。
19//! noop / increment / decrement 是指令层类型,不属于元指令层(P2-01/P0-01)。
20//!
21//! CR-20260902-001(UV-046 C2):白名单改为引用 tcb 权威常量
22//! `evorule_tcb::META_INSTRUCTION_TYPES`(SSOT)——禁止本地硬编码副本,
23//! 防 tcb 新增元指令时本命令误报合法规则(漂移防线见 tcb 单测)。
24
25use std::path::Path;
26
27use evorule_tcb::META_INSTRUCTION_TYPES;
28
29use crate::error::CliError;
30use crate::io_util;
31
32/// 执行 validate 子命令
33///
34/// # 退出码
35/// - 0:所有 transform 通过验证
36/// - 1:有 error(未知 type 或缺 type 字段)
37pub fn run(rules_dir: &Path) -> Result<(), CliError> {
38    let transforms = io_util::load_rules(rules_dir)?;
39
40    let mut total_errors = 0;
41
42    println!("=== Validating {} ===", rules_dir.display());
43    println!("Transforms: {}", transforms.len());
44    println!();
45
46    for (i, t) in transforms.iter().enumerate() {
47        let type_str = t.get("type").and_then(|v| v.as_str());
48        match type_str {
49            Some(ts) if META_INSTRUCTION_TYPES.contains(&ts) => {
50                println!("[OK]   transform[{}]: type='{}'", i, ts);
51            }
52            Some(ts) => {
53                println!(
54                    "[ERROR] transform[{}]: unknown type '{}' (not in core_eval meta-instruction whitelist)",
55                    i, ts
56                );
57                total_errors += 1;
58            }
59            None => {
60                println!("[ERROR] transform[{}]: missing 'type' field", i);
61                total_errors += 1;
62            }
63        }
64    }
65
66    println!();
67    println!("=== Summary ===");
68    println!("Errors:     {}", total_errors);
69
70    if total_errors > 0 {
71        Err(CliError::other(format!(
72            "validation failed with {} errors",
73            total_errors
74        )))
75    } else {
76        Ok(())
77    }
78}
79
80#[cfg(test)]
81mod tests {
82    #![allow(clippy::unwrap_used)]
83    use super::*;
84
85    #[test]
86    fn test_whitelist_excludes_g8_words() {
87        // 确保白名单不含 G8 禁止词(conditional/while_loop/sequence)
88        // 否则 build.rs 会拦截本文件
89        for t in META_INSTRUCTION_TYPES {
90            assert!(
91                !matches!(*t, "conditional" | "while_loop" | "sequence"),
92                "whitelist must not contain G8-forbidden words: {}",
93                t
94            );
95        }
96    }
97
98    #[test]
99    fn test_whitelist_includes_core_meta_instructions() {
100        // 确保白名单包含 core_eval 核心元指令
101        assert!(META_INSTRUCTION_TYPES.contains(&"branch"));
102        assert!(META_INSTRUCTION_TYPES.contains(&"set"));
103        assert!(META_INSTRUCTION_TYPES.contains(&"push"));
104        assert!(META_INSTRUCTION_TYPES.contains(&"io_request"));
105        assert!(META_INSTRUCTION_TYPES.contains(&"collect"));
106        assert!(META_INSTRUCTION_TYPES.contains(&"merge"));
107        // P0-01:指令层类型不得混入元指令白名单
108        assert!(!META_INSTRUCTION_TYPES.contains(&"noop"));
109        assert!(!META_INSTRUCTION_TYPES.contains(&"increment"));
110        assert!(!META_INSTRUCTION_TYPES.contains(&"decrement"));
111    }
112}