1use std::fmt;
26
27use evorule_tcb::JsonValue;
28use tracing::{debug, trace};
29
30use crate::fact::Fact;
31
32#[derive(Debug)]
36pub struct HashError {
37 message: String,
38}
39
40impl HashError {
41 fn new(message: impl Into<String>) -> Self {
42 Self {
43 message: message.into(),
44 }
45 }
46}
47
48impl fmt::Display for HashError {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 write!(f, "哈希计算错误: {}", self.message)
51 }
52}
53
54impl std::error::Error for HashError {}
55
56fn tcb_to_serde(value: &JsonValue) -> serde_json::Value {
61 match value {
62 JsonValue::Null => serde_json::Value::Null,
63 JsonValue::Bool(b) => serde_json::Value::Bool(*b),
64 JsonValue::Integer(i) => serde_json::Value::Number(serde_json::Number::from(*i)),
65 JsonValue::String(s) => serde_json::Value::String(s.to_string()),
66 JsonValue::Array(arr) => serde_json::Value::Array(arr.iter().map(tcb_to_serde).collect()),
67 JsonValue::Object(obj) => {
68 let mut map = serde_json::Map::new();
69 for (k, v) in obj.iter() {
70 map.insert(k.clone(), tcb_to_serde(v));
71 }
72 serde_json::Value::Object(map)
73 }
74 }
75}
76
77#[allow(clippy::cognitive_complexity, clippy::too_many_lines)]
92pub fn fact_to_stable_json(fact: &Fact) -> Result<serde_json::Value, HashError> {
93 let fact_type = fact.type_name();
94 let fact_id = fact.id();
95
96 trace!(
97 事实ID = ?fact_id,
98 事实类型 = %fact_type,
99 "序列化开始"
100 );
101
102 let mut obj = serde_json::Map::new();
103 match fact {
104 Fact::Command { id, instruction } => {
105 trace!(
106 事实ID = ?id,
107 指令大小 = instruction.to_string().len(),
108 "处理命令类型事实"
109 );
110 obj.insert("type".into(), serde_json::Value::String("Command".into()));
111 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
112 obj.insert("instruction".into(), tcb_to_serde(instruction));
113 }
114 Fact::PayloadUpdate { id, path, value } => {
115 trace!(
116 事实ID = ?id,
117 路径 = %path,
118 "处理载荷更新类型事实"
119 );
120 obj.insert(
121 "type".into(),
122 serde_json::Value::String("PayloadUpdate".into()),
123 );
124 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
125 obj.insert("path".into(), serde_json::Value::String(path.clone()));
126 obj.insert("value".into(), tcb_to_serde(value));
127 }
128 Fact::StateTransition {
129 id,
130 cause,
131 new_payload,
132 new_queue,
133 } => {
134 trace!(
135 事实ID = ?id,
136 原因ID = ?cause,
137 队列长度 = new_queue.len(),
138 "处理状态转换类型事实"
139 );
140 obj.insert(
141 "type".into(),
142 serde_json::Value::String("StateTransition".into()),
143 );
144 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
145 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
146 obj.insert("new_payload".into(), tcb_to_serde(new_payload));
147 obj.insert(
148 "new_queue".into(),
149 serde_json::Value::Array(new_queue.iter().map(tcb_to_serde).collect()),
150 );
151 }
152 Fact::IoRequest {
153 id,
154 cause,
155 io_type,
156 params,
157 } => {
158 trace!(
159 事实ID = ?id,
160 原因ID = ?cause,
161 IO类型 = %io_type.as_str(),
162 "处理IO请求类型事实"
163 );
164 obj.insert("type".into(), serde_json::Value::String("IoRequest".into()));
165 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
166 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
167 obj.insert(
168 "io_type".into(),
169 serde_json::Value::String(io_type.as_str().into()),
170 );
171 obj.insert("params".into(), tcb_to_serde(params));
172 }
173 Fact::IoResponse {
174 id,
175 request_id,
176 result,
177 error,
178 } => {
179 trace!(
180 事实ID = ?id,
181 请求ID = ?request_id,
182 是否有错误 = error.is_some(),
183 "处理IO响应类型事实"
184 );
185 obj.insert(
186 "type".into(),
187 serde_json::Value::String("IoResponse".into()),
188 );
189 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
190 obj.insert(
191 "request_id".into(),
192 serde_json::Value::Number(request_id.0.into()),
193 );
194 obj.insert("result".into(), tcb_to_serde(result));
195 obj.insert(
196 "error".into(),
197 error
198 .as_ref()
199 .map(|e| serde_json::Value::String(e.clone()))
200 .unwrap_or(serde_json::Value::Null),
201 );
202 }
203 Fact::Stable { id, version } => {
204 trace!(事实ID = ?id, 版本 = version, "处理稳定状态类型事实");
205 obj.insert("type".into(), serde_json::Value::String("Stable".into()));
206 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
207 obj.insert(
208 "version".into(),
209 serde_json::Value::Number((*version).into()),
210 );
211 }
212 Fact::Error { id, message } => {
213 trace!(
214 事实ID = ?id,
215 消息 = %message,
216 "处理错误类型事实"
217 );
218 obj.insert("type".into(), serde_json::Value::String("Error".into()));
219 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
220 obj.insert("message".into(), serde_json::Value::String(message.clone()));
221 }
222 Fact::TransitionTrace {
223 id,
224 cause,
225 rule_hits,
226 } => {
227 trace!(事实ID = ?id, 原因ID = ?cause, 命中数 = rule_hits.len(), "处理命中归因轨迹事实");
229 obj.insert(
230 "type".into(),
231 serde_json::Value::String("TransitionTrace".into()),
232 );
233 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
234 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
235 let hits: Vec<serde_json::Value> = rule_hits
236 .iter()
237 .map(|h| {
238 serde_json::json!({
239 "index": h.index,
240 "instr_type": h.instr_type,
241 "hit": h.hit,
242 })
243 })
244 .collect();
245 obj.insert("rule_hits".into(), serde_json::Value::Array(hits));
246 }
247 Fact::Violation {
248 id,
249 cause,
250 rule_index,
251 reason,
252 instruction,
253 } => {
254 trace!(事实ID = ?id, 原因ID = ?cause, 规则下标 = rule_index, "处理违规拦截事实");
256 obj.insert("type".into(), serde_json::Value::String("Violation".into()));
257 obj.insert("id".into(), serde_json::Value::Number(id.0.into()));
258 obj.insert("cause".into(), serde_json::Value::Number(cause.0.into()));
259 obj.insert(
260 "rule_index".into(),
261 serde_json::Value::Number((*rule_index).into()),
262 );
263 obj.insert("reason".into(), serde_json::Value::String(reason.clone()));
264 obj.insert("instruction".into(), tcb_to_serde(instruction));
265 }
266 }
267
268 let value = serde_json::Value::Object(obj);
269
270 trace!(
271 事实ID = ?fact_id,
272 事实类型 = %fact_type,
273 "序列化完成"
274 );
275
276 Ok(value)
277}
278
279#[allow(dead_code)] pub fn content_hash(value: &JsonValue) -> Result<String, HashError> {
292 #[cfg(kani)]
293 {
294 Ok(String::from("c"))
298 }
299 #[cfg(not(kani))]
300 {
301 let serde_value = tcb_to_serde(value);
302 let serialized = serde_json::to_string(&serde_value)
303 .map_err(|e| HashError::new(format!("内容哈希序列化失败: {}", e)))?;
304 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
305
306 trace!(
307 序列化长度 = serialized.len(),
308 哈希值 = %hash,
309 "内容哈希计算完成"
310 );
311
312 Ok(hash)
313 }
314}
315
316pub fn fact_hash(fact: &Fact) -> Result<String, HashError> {
340 #[cfg(kani)]
341 {
342 let id = fact.id().0;
347 let h = match id % 7 {
348 0 => "a",
349 1 => "b",
350 2 => "c",
351 3 => "d",
352 4 => "e",
353 5 => "f",
354 _ => "g",
355 };
356 Ok(String::from(h))
357 }
358 #[cfg(not(kani))]
359 {
360 let fact_type = fact.type_name();
361 let fact_id = fact.id();
362
363 debug!(
364 事实ID = ?fact_id,
365 事实类型 = %fact_type,
366 "开始计算事实哈希"
367 );
368
369 let value = fact_to_stable_json(fact)?;
370 let serialized = serde_json::to_string(&value)
371 .map_err(|e| HashError::new(format!("序列化失败: {}", e)))?;
372
373 let hash = blake3::hash(serialized.as_bytes()).to_hex().to_string();
374
375 debug!(
376 事实ID = ?fact_id,
377 事实类型 = %fact_type,
378 哈希值 = %hash,
379 序列化长度 = serialized.len(),
380 "事实哈希计算完成"
381 );
382
383 Ok(hash)
384 }
385}
386
387pub fn compute_chain_hash(facts: &[Fact]) -> Result<String, HashError> {
410 let fact_count = facts.len();
411
412 debug!(事实数量 = fact_count, "开始计算链哈希");
413
414 if fact_count == 0 {
415 debug!("事实列表为空,返回 genesis 哈希");
416 return Ok(String::from("genesis"));
417 }
418
419 let mut prev_hash = String::from("genesis");
420
421 trace!(
422 初始前序哈希 = %prev_hash,
423 "初始化前序哈希为创世值"
424 );
425
426 for (index, fact) in facts.iter().enumerate() {
427 let fact_type = fact.type_name();
428 let fact_id = fact.id();
429
430 trace!(
431 索引 = index,
432 事实ID = ?fact_id,
433 事实类型 = %fact_type,
434 前序哈希 = %prev_hash,
435 "处理事实"
436 );
437
438 let fh = fact_hash(fact)?;
439
440 trace!(
441 索引 = index,
442 事实ID = ?fact_id,
443 事实哈希 = %fh,
444 "事实哈希计算完成"
445 );
446
447 let current = chain_step(&prev_hash, &fh);
448
449 trace!(
450 索引 = index,
451 事实ID = ?fact_id,
452 当前哈希 = %current,
453 "计算当前哈希(前序哈希+事实哈希)"
454 );
455
456 prev_hash = current;
457 }
458
459 debug!(
460 事实数量 = fact_count,
461 最终哈希 = %prev_hash,
462 "链哈希计算完成"
463 );
464
465 Ok(prev_hash)
466}
467
468pub fn chain_step(prev_hash: &str, content_hash: &str) -> String {
481 #[cfg(kani)]
482 {
483 let mut s = String::from(prev_hash);
487 s.push_str(content_hash);
488 s
489 }
490 #[cfg(not(kani))]
491 {
492 let combined = format!("{}{}", prev_hash, content_hash);
493 blake3::hash(combined.as_bytes()).to_hex().to_string()
494 }
495}
496
497#[cfg(test)]
498mod tests {
499 #![allow(clippy::unwrap_used, clippy::panic, clippy::expect_used)]
500 use super::*;
501 use crate::fact::{Fact, FactId, IoType};
502 use evorule_tcb::JsonValue;
503
504 #[test]
505 fn test_fact_to_stable_json_format() {
506 let command = Fact::Command {
507 id: FactId(1),
508 instruction: JsonValue::object_from_pairs(&[
509 ("type", JsonValue::string("increment")),
510 (
511 "params",
512 JsonValue::object_from_pairs(&[
513 ("attr", JsonValue::string("x")),
514 ("delta", JsonValue::Integer(5)),
515 ]),
516 ),
517 ]),
518 };
519
520 let json_value = fact_to_stable_json(&command).unwrap();
521
522 assert_eq!(json_value.get("type").unwrap().as_str().unwrap(), "Command");
523 assert_eq!(json_value.get("id").unwrap().as_u64().unwrap(), 1);
524 assert!(json_value.get("instruction").is_some());
525 }
526
527 #[test]
528 fn test_fact_hash_all_variants() {
529 let test_facts = vec![
530 Fact::Command {
531 id: FactId(1),
532 instruction: JsonValue::empty_object(),
533 },
534 Fact::PayloadUpdate {
535 id: FactId(2),
536 path: "test.path".into(),
537 value: JsonValue::string("test_value"),
538 },
539 Fact::StateTransition {
540 id: FactId(3),
541 cause: FactId(1),
542 new_payload: JsonValue::empty_object(),
543 new_queue: vec![],
544 },
545 Fact::IoRequest {
546 id: FactId(4),
547 cause: FactId(3),
548 io_type: IoType::http_get(),
549 params: JsonValue::empty_object(),
550 },
551 Fact::IoResponse {
552 id: FactId(5),
553 request_id: FactId(4),
554 result: JsonValue::string("response"),
555 error: None,
556 },
557 Fact::IoResponse {
558 id: FactId(6),
559 request_id: FactId(4),
560 result: JsonValue::Null,
561 error: Some("timeout".to_string()),
562 },
563 Fact::Stable {
564 id: FactId(7),
565 version: 0,
566 },
567 Fact::Error {
568 id: FactId(8),
569 message: "test error".into(),
570 },
571 ];
572
573 for fact in test_facts {
574 let hash = fact_hash(&fact).unwrap();
575 assert_eq!(hash.len(), 64);
576
577 let hash2 = fact_hash(&fact).unwrap();
578 assert_eq!(
579 hash,
580 hash2,
581 "fact_hash should be deterministic for {}",
582 fact.type_name()
583 );
584 }
585 }
586
587 #[test]
588 fn test_fact_hash_identity() {
589 let fact1 = Fact::Command {
590 id: FactId(1),
591 instruction: JsonValue::string("same"),
592 };
593 let fact2 = Fact::Command {
594 id: FactId(1),
595 instruction: JsonValue::string("same"),
596 };
597
598 assert_eq!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
599 }
600
601 #[test]
602 fn test_fact_hash_different_ids() {
603 let fact1 = Fact::Command {
604 id: FactId(1),
605 instruction: JsonValue::string("same"),
606 };
607 let fact2 = Fact::Command {
608 id: FactId(2),
609 instruction: JsonValue::string("same"),
610 };
611
612 assert_ne!(fact_hash(&fact1).unwrap(), fact_hash(&fact2).unwrap());
613 }
614
615 #[test]
616 fn test_fact_hash_snapshot() {
617 let test_facts = [
618 Fact::Command {
619 id: FactId(1),
620 instruction: JsonValue::empty_object(),
621 },
622 Fact::PayloadUpdate {
623 id: FactId(2),
624 path: "test.path".into(),
625 value: JsonValue::string("test_value"),
626 },
627 Fact::StateTransition {
628 id: FactId(3),
629 cause: FactId(1),
630 new_payload: JsonValue::empty_object(),
631 new_queue: vec![],
632 },
633 Fact::IoRequest {
634 id: FactId(4),
635 cause: FactId(3),
636 io_type: IoType::http_get(),
637 params: JsonValue::empty_object(),
638 },
639 Fact::IoResponse {
640 id: FactId(5),
641 request_id: FactId(4),
642 result: JsonValue::string("response"),
643 error: None,
644 },
645 Fact::Stable {
646 id: FactId(6),
647 version: 0,
648 },
649 Fact::Error {
650 id: FactId(7),
651 message: "test error".into(),
652 },
653 ];
654
655 let current_hashes: Vec<String> =
656 test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
657 let snapshot_file = env!("CARGO_MANIFEST_DIR").to_string() + "/hash_snapshot.txt";
658
659 if std::path::Path::new(&snapshot_file).exists() {
660 let snapshot = std::fs::read_to_string(&snapshot_file).unwrap();
661 let expected_hashes: Vec<String> = snapshot.lines().map(|s| s.to_string()).collect();
662 assert_eq!(
663 current_hashes, expected_hashes,
664 "Hash snapshot mismatch! If this is an expected change (e.g., Fact struct modification), delete {} to regenerate.",
665 snapshot_file
666 );
667 } else {
668 let snapshot_content = current_hashes.join("\n");
669 std::fs::write(&snapshot_file, snapshot_content).unwrap();
670 println!("Created hash snapshot: {}", snapshot_file);
671 }
672 }
673
674 #[test]
675 fn test_compute_chain_hash_empty() {
676 let facts: Vec<Fact> = vec![];
677 let chain_hash = compute_chain_hash(&facts).unwrap();
678 assert_eq!(chain_hash, "genesis");
679 }
680
681 #[test]
682 fn test_compute_chain_hash_deterministic() {
683 let facts = vec![
684 Fact::Command {
685 id: FactId(1),
686 instruction: JsonValue::empty_object(),
687 },
688 Fact::StateTransition {
689 id: FactId(2),
690 cause: FactId(1),
691 new_payload: JsonValue::empty_object(),
692 new_queue: vec![],
693 },
694 ];
695
696 let result1 = compute_chain_hash(&facts).unwrap();
697 let result2 = compute_chain_hash(&facts).unwrap();
698 assert_eq!(result1, result2);
699 }
700
701 #[test]
702 fn test_compute_chain_hash_order_sensitive() {
703 let fact1 = Fact::Command {
704 id: FactId(1),
705 instruction: JsonValue::empty_object(),
706 };
707 let fact2 = Fact::StateTransition {
708 id: FactId(2),
709 cause: FactId(1),
710 new_payload: JsonValue::empty_object(),
711 new_queue: vec![],
712 };
713
714 let chain1 = compute_chain_hash(&[fact1.clone(), fact2.clone()]).unwrap();
715 let chain2 = compute_chain_hash(&[fact2, fact1]).unwrap();
716 assert_ne!(chain1, chain2, "链哈希应对 Fact 顺序敏感");
717 }
718
719 #[test]
724 fn test_cross_validate_with_tier2() {
725 let test_facts = [
727 Fact::Command {
728 id: FactId(1),
729 instruction: JsonValue::empty_object(),
730 },
731 Fact::PayloadUpdate {
732 id: FactId(2),
733 path: "test.path".into(),
734 value: JsonValue::string("test_value"),
735 },
736 Fact::StateTransition {
737 id: FactId(3),
738 cause: FactId(1),
739 new_payload: JsonValue::empty_object(),
740 new_queue: vec![],
741 },
742 Fact::IoRequest {
743 id: FactId(4),
744 cause: FactId(3),
745 io_type: IoType::http_get(),
746 params: JsonValue::empty_object(),
747 },
748 Fact::IoResponse {
749 id: FactId(5),
750 request_id: FactId(4),
751 result: JsonValue::string("response"),
752 error: None,
753 },
754 Fact::Stable {
755 id: FactId(6),
756 version: 0,
757 },
758 Fact::Error {
759 id: FactId(7),
760 message: "test error".into(),
761 },
762 ];
763
764 let tier1_hashes: Vec<String> = test_facts.iter().map(|f| fact_hash(f).unwrap()).collect();
766
767 let tier2_snapshot_path = "../../evorule-governance/src/hash_snapshot.txt".to_string();
769 if std::path::Path::new(&tier2_snapshot_path).exists() {
770 let tier2_snapshot = std::fs::read_to_string(&tier2_snapshot_path).unwrap();
771 let tier2_hashes: Vec<String> = tier2_snapshot.lines().map(|s| s.to_string()).collect();
772
773 assert_eq!(
774 tier1_hashes, tier2_hashes,
775 "tier1 和 tier2 的 fact_hash 不一致!\
776 如果这是预期变更(如 Fact 结构修改),\
777 请删除 evorule-governance/src/hash_snapshot.txt 重新生成。"
778 );
779 }
780 }
781}