Skip to main content

ergo_runtime/common/
intent_id.rs

1use sha2::{Digest, Sha256};
2
3/// Derive a deterministic correlation ID for an emitted intent.
4pub fn derive_intent_id(
5    graph_id: &str,
6    event_id: &str,
7    node_runtime_id: &str,
8    intent_kind: &str,
9    intent_ordinal: usize,
10) -> String {
11    let version_tag = "eid1";
12    let ordinal = intent_ordinal.to_string();
13    let mut bytes = Vec::new();
14    for segment in [
15        version_tag,
16        graph_id,
17        event_id,
18        node_runtime_id,
19        intent_kind,
20        ordinal.as_str(),
21    ] {
22        push_len_prefixed(&mut bytes, segment);
23    }
24
25    let mut hasher = Sha256::new();
26    hasher.update(&bytes);
27    let digest = hasher.finalize();
28    format!("eid1:sha256:{}", to_hex(&digest))
29}
30
31fn push_len_prefixed(out: &mut Vec<u8>, segment: &str) {
32    let bytes = segment.as_bytes();
33    let len = u32::try_from(bytes.len()).expect("intent_id segment exceeds u32 max length");
34    out.extend_from_slice(&len.to_be_bytes());
35    out.extend_from_slice(bytes);
36}
37
38fn to_hex(bytes: &[u8]) -> String {
39    let mut out = String::with_capacity(bytes.len() * 2);
40    for byte in bytes {
41        use std::fmt::Write as _;
42        let _ = write!(&mut out, "{byte:02x}");
43    }
44    out
45}
46
47#[cfg(test)]
48mod tests {
49    use super::derive_intent_id;
50
51    #[test]
52    fn derive_intent_id_is_deterministic() {
53        let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
54        let id2 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
55        assert_eq!(id1, id2);
56    }
57
58    #[test]
59    fn derive_intent_id_changes_with_event_id() {
60        let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
61        let id2 = derive_intent_id("g1", "evt2", "n0", "place_order", 0);
62        assert_ne!(id1, id2);
63    }
64
65    #[test]
66    fn derive_intent_id_changes_with_node_runtime_id() {
67        let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
68        let id2 = derive_intent_id("g1", "evt1", "n1", "place_order", 0);
69        assert_ne!(id1, id2);
70    }
71
72    #[test]
73    fn derive_intent_id_changes_with_intent_ordinal() {
74        let id1 = derive_intent_id("g1", "evt1", "n0", "place_order", 0);
75        let id2 = derive_intent_id("g1", "evt1", "n0", "place_order", 1);
76        assert_ne!(id1, id2);
77    }
78
79    #[test]
80    fn derive_intent_id_golden_regression() {
81        let actual = derive_intent_id("graph_alpha", "evt_001", "n42", "place_order", 3);
82        let expected =
83            "eid1:sha256:157aed720bd20c1712cede0a499cf6607f687401a29d70ce5df5d2778f85a9fa";
84        assert_eq!(actual, expected);
85    }
86}