Skip to main content

evorule_cli/commands/
run.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 run` —— 加载并执行 JSON 规则,输出 fact log
5//!
6//! # 流程
7//! 1. `io_util::load_rules`:加载规则目录(确定性排序)
8//! 2. `io_util::parse_initial_payload`:解析初始 payload
9//! 3. 构造初始指令 `{"type":"noop"}` 触发 transform 链
10//! 4. `executor::execute`:同步反应器循环(FIFO + max_steps)
11//! 5. `fact_log::write_facts`:输出 fact log(tier1 WAL 格式)
12//!
13//! # fact log 格式
14//! 与 evorule-reactor WAL 文件格式互换,与 evorule-governance auditor 哈希链互通。
15
16use std::path::Path;
17
18use evorule_tcb::JsonValue;
19
20use crate::error::CliError;
21use crate::{executor, fact_log, io_util};
22
23/// 执行 run 子命令
24///
25/// # 参数
26/// - `rules_dir`:规则目录
27/// - `payload`:初始 payload JSON 字符串(可选)
28/// - `payload_file`:初始 payload 文件(可选,优先级低于 payload)
29/// - `output`:输出文件路径(None 则 stdout)
30/// - `max_steps`:最大执行步数上界
31pub fn run(
32    rules_dir: &Path,
33    payload: Option<&str>,
34    payload_file: Option<&Path>,
35    output: Option<&Path>,
36    max_steps: usize,
37) -> Result<(), CliError> {
38    let transforms = io_util::load_rules(rules_dir)?;
39    let initial_payload = io_util::parse_initial_payload(payload, payload_file)?;
40
41    // 初始指令:noop 触发 transform 链
42    // (core_eval 规则通过 branch domain=instruction_type 匹配 noop 后执行 on_true)
43    let initial_instruction = JsonValue::object_from_pairs(&[("type", JsonValue::string("noop"))]);
44
45    tracing::info!(rules = transforms.len(), max_steps, "starting execution");
46
47    // 最终 payload 经返回值直接交付(CR-20260901-001:Stable 不再内嵌快照)
48    let (facts, _final_payload) =
49        executor::execute(&transforms, initial_payload, initial_instruction, max_steps)?;
50
51    // 输出 fact log(tier1 WAL 格式)
52    fact_log::write_facts(output, &facts)?;
53
54    // stderr 摘要(不影响 fact log 输出)
55    let has_error = facts
56        .iter()
57        .any(|f| matches!(f, evorule_reactor::Fact::Error { .. }));
58    if has_error {
59        tracing::warn!(facts = facts.len(), "execution completed with Error facts");
60    } else {
61        tracing::info!(facts = facts.len(), "execution completed successfully");
62    }
63
64    Ok(())
65}