use crate::error::ReactorError;
use crate::fact::{FactId, IoType};
use crate::invariants::InvariantViolation;
use crate::state::ReactorState;
use evorule_tcb::{execute_transition, JsonValue, TransitionResult};
#[derive(Debug, Clone)]
pub(crate) enum StepOutcome {
StateChanged,
Ignored {
instruction_type: String,
reason: String,
},
IoRequired {
io_type: String,
params: JsonValue,
},
TcbError(String),
}
pub(crate) fn next_step(
core_eval: &[JsonValue],
state: &mut ReactorState,
max_queue_len: usize,
) -> Option<StepOutcome> {
let (instruction, cause) = state.pop_instruction()?;
let queue_vec: Vec<JsonValue> = state.queue.iter().cloned().collect();
let result = execute_transition(core_eval, &instruction, &state.payload, &queue_vec);
match result {
Ok(TransitionResult::State {
new_payload,
new_queue,
}) => {
state.payload = new_payload;
state.update_queue_with_causes(new_queue, cause);
if state.io_recovery {
state.clear_io_result();
state.io_recovery = false;
}
state.bump_version();
if max_queue_len > 0 && state.queue.len() >= max_queue_len {
state.clear_queue();
}
Some(StepOutcome::StateChanged)
}
Ok(TransitionResult::Ignored {
instruction_type,
reason,
}) => {
state.bump_version();
tracing::warn!(
instruction_type = %instruction_type,
reason = %reason,
"指令被忽略:没有匹配的宪法规则或规则产生了 noop 效果"
);
Some(StepOutcome::Ignored {
instruction_type,
reason,
})
}
Ok(TransitionResult::IoRequired { io_type, params }) => {
state.push_front(instruction, cause);
Some(StepOutcome::IoRequired { io_type, params })
}
Err(err) => Some(StepOutcome::TcbError(err.to_string())),
}
}
pub(crate) fn apply_command(state: &mut ReactorState, instruction: JsonValue, cause: FactId) {
state.push_back(instruction, cause);
}
pub(crate) fn apply_payload_update(
state: &mut ReactorState,
path: &str,
value: JsonValue,
) -> Result<(), ReactorError> {
if let Some(target) = evorule_tcb::path::resolve_path_mut(&mut state.payload, path) {
*target = value;
state.bump_version();
return Ok(());
}
if !path.contains('.') && !path.contains('[') {
if let JsonValue::Object(map) = &mut state.payload {
map.insert(path.to_string(), value);
state.bump_version();
return Ok(());
}
}
Err(ReactorError::InvalidState {
field: "payload path does not exist",
})
}
pub(crate) fn apply_io_response(
state: &mut ReactorState,
request_id: FactId,
result: JsonValue,
) -> Result<bool, ReactorError> {
let io_type = state.get_io_type(&request_id).cloned();
if !state.complete_io_request(request_id) {
return Ok(false);
}
if result.is_null() {
state.take_io_instruction(request_id);
state.bump_version();
return Ok(true);
}
if let Some(io_type) = io_type {
inject_io_result(state, &io_type, result)?;
}
if let Some((orig_instruction, orig_cause)) = state.take_io_instruction(request_id) {
state.push_front(orig_instruction, orig_cause);
state.io_recovery = true;
}
state.bump_version();
Ok(true)
}
fn inject_io_result(
state: &mut ReactorState,
io_type: &IoType,
result: JsonValue,
) -> Result<(), ReactorError> {
#[cfg(kani)]
{
let _ = (result, io_type); state.kani_has_io_result = true;
return Ok(());
}
#[cfg(not(kani))]
{
if let JsonValue::Object(map) = &mut state.payload {
let entry = map
.entry("__io_results__".to_string())
.or_insert_with(JsonValue::empty_object);
if let JsonValue::Object(io_map) = entry {
io_map.insert(io_type.as_str().to_string(), result);
return Ok(());
}
}
Err(ReactorError::InvalidState {
field: "payload is not an object, cannot inject __io_results__",
})
}
}
pub(crate) fn check_invariants(state: &ReactorState, steps: usize) -> Vec<InvariantViolation> {
crate::invariants::check_invariants(state, steps)
}
pub(crate) fn is_stable(queue_len: usize, pending_io_count: usize, steps: usize) -> bool {
queue_len == 0 && pending_io_count == 0 && steps > 0
}
pub(crate) fn register_io_request_pure(
state: &mut ReactorState,
id: FactId,
io_type: IoType,
instruction: JsonValue,
) {
if state.pending_requests.insert(id) {
state.pending_io_count = state.pending_io_count.saturating_add(1);
state.pending_io_types.insert(id, io_type);
state
.pending_io_instructions
.insert(id, (instruction, FactId(0)));
}
}
#[cfg(kani)]
#[path = "../verification/kani_proofs.rs"]
pub mod kani_proofs;
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used)]
use super::*;
use crate::fact::FactId;
fn state_with_queue(instructions: Vec<JsonValue>) -> ReactorState {
let mut state = ReactorState::new();
for instr in instructions {
state.push_back(instr, FactId(0));
}
state
}
fn increment_instr(attr: &str, delta: i64) -> JsonValue {
use std::collections::BTreeMap;
let mut params = BTreeMap::new();
params.insert("attr".to_string(), JsonValue::string(attr));
params.insert("delta".to_string(), JsonValue::Integer(delta));
let mut instr = BTreeMap::new();
instr.insert("type".to_string(), JsonValue::string("increment"));
instr.insert("params".to_string(), JsonValue::Object(params));
JsonValue::Object(instr)
}
fn load_core_eval() -> Vec<JsonValue> {
use std::path::PathBuf;
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let path = manifest_dir.join("../evorule-tcb/core_eval.json");
let json_str = std::fs::read_to_string(&path).unwrap();
let json: serde_json::Value = serde_json::from_str(&json_str).unwrap();
let transform = json.get("transform").and_then(|v| v.as_array()).unwrap();
fn serde_to_tcb(v: serde_json::Value) -> JsonValue {
match v {
serde_json::Value::Null => JsonValue::Null,
serde_json::Value::Bool(b) => JsonValue::Bool(b),
serde_json::Value::Number(n) => {
if let Some(i) = n.as_i64() {
JsonValue::Integer(i)
} else {
JsonValue::string(n.to_string())
}
}
serde_json::Value::String(s) => JsonValue::string(s),
serde_json::Value::Array(arr) => {
JsonValue::Array(arr.into_iter().map(serde_to_tcb).collect())
}
serde_json::Value::Object(obj) => {
let mut map = std::collections::BTreeMap::new();
for (k, v) in obj {
map.insert(k, serde_to_tcb(v));
}
JsonValue::Object(map)
}
}
}
transform.iter().cloned().map(serde_to_tcb).collect()
}
#[test]
fn test_next_step_state_changed() {
let core_eval = load_core_eval();
let mut state = state_with_queue(vec![increment_instr("x", 5)]);
let outcome = next_step(&core_eval, &mut state, 1000);
assert!(matches!(outcome, Some(StepOutcome::StateChanged)));
assert_eq!(state.payload.get("x"), Some(&JsonValue::Integer(5)));
assert!(state.queue.is_empty());
}
#[test]
fn test_next_step_empty_queue() {
let core_eval = load_core_eval();
let mut state = ReactorState::new();
let outcome = next_step(&core_eval, &mut state, 1000);
assert!(outcome.is_none());
}
#[test]
fn test_next_step_version_increases() {
let core_eval = load_core_eval();
let mut state = state_with_queue(vec![increment_instr("x", 1)]);
let prev_version = state.version;
let _ = next_step(&core_eval, &mut state, 1000);
assert!(state.version > prev_version);
assert_eq!(state.prev_version, prev_version);
}
#[test]
fn test_apply_command() {
let mut state = ReactorState::new();
let instr = increment_instr("y", 3);
apply_command(&mut state, instr.clone(), FactId(0));
assert_eq!(state.queue.len(), 1);
assert_eq!(state.queue.front(), Some(&instr));
}
#[test]
fn test_apply_payload_update_existing_path() {
let mut state = ReactorState::new();
if let JsonValue::Object(map) = &mut state.payload {
map.insert("x".to_string(), JsonValue::Integer(0));
}
let result = apply_payload_update(&mut state, "x", JsonValue::Integer(42));
assert!(result.is_ok());
assert_eq!(state.payload.get("x"), Some(&JsonValue::Integer(42)));
assert_eq!(state.version, 1);
}
#[test]
fn test_apply_payload_update_new_top_level() {
let mut state = ReactorState::new();
let result = apply_payload_update(&mut state, "new_field", JsonValue::Integer(100));
assert!(result.is_ok());
assert_eq!(
state.payload.get("new_field"),
Some(&JsonValue::Integer(100))
);
}
#[test]
fn test_apply_payload_update_nonexistent_nested() {
let mut state = ReactorState::new();
let result = apply_payload_update(&mut state, "a.b.c", JsonValue::Integer(1));
assert!(result.is_err());
}
#[test]
fn test_apply_io_response_unknown_id() {
let mut state = ReactorState::new();
let result = apply_io_response(&mut state, FactId(999), JsonValue::Null);
assert!(result.is_ok());
assert!(!result.unwrap());
}
#[test]
fn test_apply_io_response_known_id() {
let mut state = ReactorState::new();
let id = FactId(1);
let instr = increment_instr("x", 1);
state.register_io_request(id, IoType::call_external());
state.save_io_instruction(id, instr.clone(), FactId(0));
let result = apply_io_response(&mut state, id, JsonValue::string("result"));
assert!(result.is_ok());
assert!(result.unwrap());
assert!(matches!(
state
.payload
.get("__io_results__")
.and_then(|r| r.get("call_external")),
Some(JsonValue::String(_))
));
assert!(state.io_recovery);
assert_eq!(state.queue.front(), Some(&instr));
assert_eq!(state.pending_io_count, 0);
}
#[test]
fn test_is_stable() {
assert!(!is_stable(0, 0, 0));
assert!(is_stable(0, 0, 1));
assert!(!is_stable(1, 0, 1));
assert!(!is_stable(0, 1, 1));
assert!(!is_stable(1, 1, 1));
}
#[test]
fn test_check_invariants_fresh_state() {
let state = ReactorState::new();
let violations = check_invariants(&state, 0);
assert!(violations.is_empty());
}
#[test]
fn test_register_io_request_pure() {
let mut state = ReactorState::new();
let id = FactId(1);
let instr = increment_instr("x", 1);
register_io_request_pure(&mut state, id, IoType::call_external(), instr.clone());
assert_eq!(state.pending_io_count, 1);
assert!(state.pending_requests.contains(&id));
assert_eq!(
state.pending_io_types.get(&id),
Some(&IoType::call_external())
);
assert_eq!(
state.pending_io_instructions.get(&id),
Some(&(instr, FactId(0)))
);
assert!(!state.pending_io_timestamps.contains_key(&id));
}
#[test]
fn test_register_io_request_pure_idempotent() {
let mut state = ReactorState::new();
let id = FactId(1);
let instr = increment_instr("x", 1);
register_io_request_pure(&mut state, id, IoType::call_external(), instr.clone());
register_io_request_pure(&mut state, id, IoType::call_external(), instr.clone());
assert_eq!(state.pending_io_count, 1);
}
}