Skip to main content

vyre_runtime/pipeline_cache/
fingerprint.rs

1//! Content-addressed cache identity for one authenticated neutral artifact.
2
3use vyre_megakernel::Artifact;
4
5/// The exact canonical artifact digest used as a pipeline-cache key.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct PipelineFingerprint(pub [u8; 32]);
8
9const _: fn(&Artifact) -> PipelineFingerprint = PipelineFingerprint::of;
10
11impl PipelineFingerprint {
12    /// Derive the cache fingerprint from the authenticated neutral artifact identity.
13    ///
14    /// Dispatch inputs, device generation, and runtime policy cannot alter this
15    /// key. Target payload format and device identity belong in cache metadata.
16    #[must_use]
17    pub fn of(artifact: &Artifact) -> Self {
18        Self(artifact.digest().0)
19    }
20
21    /// Hex-encode the fingerprint for human display + path-safe
22    /// storage. Lowercase, no separators, 64 chars.
23    #[must_use]
24    pub fn hex(&self) -> String {
25        let mut out = String::with_capacity(64);
26        self.push_hex(&mut out);
27        out
28    }
29
30    pub(super) fn push_hex(&self, out: &mut String) {
31        const HEX: &[u8; 16] = b"0123456789abcdef";
32        for &byte in &self.0 {
33            out.push(HEX[(byte >> 4) as usize] as char);
34            out.push(HEX[(byte & 0x0f) as usize] as char);
35        }
36    }
37}
38
39#[cfg(test)]
40mod tests {
41    use super::*;
42    use crate::pipeline_cache::test_helpers::{artifact_for_program, tiny_artifact};
43    use vyre_foundation::ir::{BufferDecl, DataType, Expr, Node, Program};
44
45    #[test]
46    fn fingerprint_is_deterministic() {
47        let a = PipelineFingerprint::of(&tiny_artifact());
48        let b = PipelineFingerprint::of(&tiny_artifact());
49        assert_eq!(a, b);
50    }
51
52    #[test]
53    fn fingerprint_hex_is_64_chars() {
54        let fp = PipelineFingerprint::of(&tiny_artifact());
55        assert_eq!(fp.hex().len(), 64);
56    }
57
58    #[test]
59    fn distinct_artifacts_do_not_share_fingerprint() {
60        let first = tiny_artifact();
61        let second = artifact_for_program(Program::wrapped(
62            vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
63            [1, 1, 1],
64            vec![Node::store("out", Expr::u32(0), Expr::u32(43))],
65        ));
66        assert_ne!(
67            PipelineFingerprint::of(&first),
68            PipelineFingerprint::of(&second)
69        );
70    }
71
72    #[test]
73    fn fingerprint_changes_when_declared_program_shape_changes() {
74        let base = tiny_artifact();
75        let widened = artifact_for_program(Program::wrapped(
76            vec![BufferDecl::read_write("out", 0, DataType::U32).with_count(1)],
77            [64, 1, 1],
78            vec![Node::store("out", Expr::u32(0), Expr::u32(42))],
79        ));
80
81        assert_ne!(
82            PipelineFingerprint::of(&base),
83            PipelineFingerprint::of(&widened),
84            "neutral artifact geometry must change the fingerprint"
85        );
86    }
87}