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(L95-105)处理的
18//! 元指令类型:branch / set / push / io_request / collect / merge。
19//! noop / increment / decrement 是指令层类型,不属于元指令层(P2-01/P0-01)。
20
21use std::path::Path;
22
23use crate::error::CliError;
24use crate::io_util;
25
26/// 合法 core_eval 元指令类型白名单
27///
28/// 来源:evorule-tcb/src/executor.rs::execute_meta_instruction 的 dispatch(L95-105),
29/// 仅 6 种:branch / set / push / io_request / collect / merge。
30/// noop/increment/decrement 是**指令层(instruction)**类型,不属于元指令层,
31/// 不得混入本白名单(P0-01;修前曾误混导致假阳性)。
32/// 不含 G8 禁止词(conditional/while_loop/sequence),故无需 build.rs 豁免。
33const VALID_TRANSFORM_TYPES: &[&str] = &[
34    "branch",
35    "set",
36    "push",
37    "io_request",
38    "collect",
39    "merge",
40];
41
42/// 执行 validate 子命令
43///
44/// # 退出码
45/// - 0:所有 transform 通过验证
46/// - 1:有 error(未知 type 或缺 type 字段)
47pub fn run(rules_dir: &Path) -> Result<(), CliError> {
48    let transforms = io_util::load_rules(rules_dir)?;
49
50    let mut total_errors = 0;
51
52    println!("=== Validating {} ===", rules_dir.display());
53    println!("Transforms: {}", transforms.len());
54    println!();
55
56    for (i, t) in transforms.iter().enumerate() {
57        let type_str = t.get("type").and_then(|v| v.as_str());
58        match type_str {
59            Some(ts) if VALID_TRANSFORM_TYPES.contains(&ts) => {
60                println!("[OK]   transform[{}]: type='{}'", i, ts);
61            }
62            Some(ts) => {
63                println!(
64                    "[ERROR] transform[{}]: unknown type '{}' (not in core_eval meta-instruction whitelist)",
65                    i, ts
66                );
67                total_errors += 1;
68            }
69            None => {
70                println!("[ERROR] transform[{}]: missing 'type' field", i);
71                total_errors += 1;
72            }
73        }
74    }
75
76    println!();
77    println!("=== Summary ===");
78    println!("Errors:     {}", total_errors);
79
80    if total_errors > 0 {
81        Err(CliError::other(format!(
82            "validation failed with {} errors",
83            total_errors
84        )))
85    } else {
86        Ok(())
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    #![allow(clippy::unwrap_used)]
93    use super::*;
94
95    #[test]
96    fn test_whitelist_excludes_g8_words() {
97        // 确保白名单不含 G8 禁止词(conditional/while_loop/sequence)
98        // 否则 build.rs 会拦截本文件
99        for t in VALID_TRANSFORM_TYPES {
100            assert!(
101                !matches!(*t, "conditional" | "while_loop" | "sequence"),
102                "whitelist must not contain G8-forbidden words: {}",
103                t
104            );
105        }
106    }
107
108    #[test]
109    fn test_whitelist_includes_core_meta_instructions() {
110        // 确保白名单包含 core_eval 核心元指令
111        assert!(VALID_TRANSFORM_TYPES.contains(&"branch"));
112        assert!(VALID_TRANSFORM_TYPES.contains(&"set"));
113        assert!(VALID_TRANSFORM_TYPES.contains(&"push"));
114        assert!(VALID_TRANSFORM_TYPES.contains(&"io_request"));
115        assert!(VALID_TRANSFORM_TYPES.contains(&"collect"));
116        assert!(VALID_TRANSFORM_TYPES.contains(&"merge"));
117        // P0-01:指令层类型不得混入元指令白名单
118        assert!(!VALID_TRANSFORM_TYPES.contains(&"noop"));
119        assert!(!VALID_TRANSFORM_TYPES.contains(&"increment"));
120        assert!(!VALID_TRANSFORM_TYPES.contains(&"decrement"));
121    }
122}