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 的 `META_INSTRUCTION_TYPES`(SSOT)
18//! 元指令类型:branch / set / push / io_request / enforce
19//!(collect/merge 已退役,规则清理方案 2026-09-14)。
20//! noop / increment / decrement 是指令层类型,不属于元指令层(P2-01/P0-01)。
21//!
22//!(C2):白名单改为引用 tcb 权威常量
23//! `evorule_tcb::META_INSTRUCTION_TYPES`(SSOT)——禁止本地硬编码副本,
24//! 防 tcb 新增元指令时本命令误报合法规则(漂移防线见 tcb 单测)。
25
26use std::path::Path;
27
28use evorule_tcb::META_INSTRUCTION_TYPES;
29
30use crate::error::CliError;
31use crate::io_util;
32
33/// 执行 validate 子命令
34///
35/// # 退出码
36/// - 0:所有 transform 通过验证
37/// - 1:有 error(未知 type 或缺 type 字段)
38pub fn run(rules_dir: &Path) -> Result<(), CliError> {
39    let transforms = io_util::load_rules(rules_dir)?;
40
41    let mut total_errors = 0;
42
43    println!("=== Validating {} ===", rules_dir.display());
44    println!("Transforms: {}", transforms.len());
45    println!();
46
47    for (i, t) in transforms.iter().enumerate() {
48        let type_str = t.get("type").and_then(|v| v.as_str());
49        match type_str {
50            Some(ts) if META_INSTRUCTION_TYPES.contains(&ts) => {
51                println!("[OK]   transform[{}]: type='{}'", i, ts);
52            }
53            Some(ts) => {
54                println!(
55                    "[ERROR] transform[{}]: unknown type '{}' (not in core_eval meta-instruction whitelist)",
56                    i, ts
57                );
58                total_errors += 1;
59            }
60            None => {
61                println!("[ERROR] transform[{}]: missing 'type' field", i);
62                total_errors += 1;
63            }
64        }
65    }
66
67    println!();
68    println!("=== Summary ===");
69    println!("Errors:     {}", total_errors);
70
71    if total_errors > 0 {
72        Err(CliError::other(format!(
73            "validation failed with {} errors",
74            total_errors
75        )))
76    } else {
77        Ok(())
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    #![allow(clippy::unwrap_used)]
84    use super::*;
85
86    #[test]
87    fn test_whitelist_excludes_g8_words() {
88        // 确保白名单不含 G8 禁止词(conditional/while_loop/sequence)
89        // 否则 build.rs 会拦截本文件
90        for t in META_INSTRUCTION_TYPES {
91            assert!(
92                !matches!(*t, "conditional" | "while_loop" | "sequence"),
93                "whitelist must not contain G8-forbidden words: {}",
94                t
95            );
96        }
97    }
98
99    #[test]
100    fn test_whitelist_includes_core_meta_instructions() {
101        // 确保白名单包含 core_eval 核心元指令
102        assert!(META_INSTRUCTION_TYPES.contains(&"branch"));
103        assert!(META_INSTRUCTION_TYPES.contains(&"set"));
104        assert!(META_INSTRUCTION_TYPES.contains(&"push"));
105        assert!(META_INSTRUCTION_TYPES.contains(&"io_request"));
106        assert!(META_INSTRUCTION_TYPES.contains(&"enforce"));
107        // collect/merge 已退役,不得回流白名单
108        assert!(!META_INSTRUCTION_TYPES.contains(&"collect"));
109        assert!(!META_INSTRUCTION_TYPES.contains(&"merge"));
110        // P0-01:指令层类型不得混入元指令白名单
111        assert!(!META_INSTRUCTION_TYPES.contains(&"noop"));
112        assert!(!META_INSTRUCTION_TYPES.contains(&"increment"));
113        assert!(!META_INSTRUCTION_TYPES.contains(&"decrement"));
114    }
115}