katra-core 0.1.0

Katra3D core: shared vocabulary, error model, event model, IDs, and policy types.
Documentation
//! Deterministic hashing (FNV-1a 64).
//!
//! Katra3D uses fingerprints everywhere — file identity, access patterns,
//! cache keys, semantic graph identity. Fingerprints must be **stable across
//! runs, machines, and architectures**, so we implement FNV-1a directly
//! instead of relying on randomized or platform-dependent hashers.
//!
//! FNV-1a is *not* a cryptographic hash. It is deliberately simple,
//! auditable, and deterministic (KatraFlow §10: "simple, auditable
//! techniques").

/// FNV-1a 64-bit over raw bytes. `const` so schema hashes can be computed at
/// compile time.
pub const fn fnv1a(bytes: &[u8]) -> u64 {
    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
    let mut i = 0usize;
    while i < bytes.len() {
        h ^= bytes[i] as u64;
        h = h.wrapping_mul(0x0000_0100_0000_01b3);
        i += 1;
    }
    h
}

/// FNV-1a over a string.
pub fn fnv1a_str(s: &str) -> u64 {
    fnv1a(s.as_bytes())
}

/// FNV-1a over a sequence of 64-bit parts (each part folded in little-endian).
pub fn fnv1a_parts(parts: &[u64]) -> u64 {
    let mut h: u64 = 0xcbf2_9ce4_8422_2325;
    for &p in parts {
        let mut shift = 0usize;
        while shift < 8 {
            h ^= (p >> (shift * 8)) & 0xff;
            h = h.wrapping_mul(0x0000_0100_0000_01b3);
            shift += 1;
        }
    }
    h
}

/// Combine two hashes (for rolling fingerprints).
pub fn fnv1a_combine(acc: u64, next: u64) -> u64 {
    let mut h = acc ^ 0x9e37_79b9_7f4a_7c15;
    let mut shift = 0usize;
    while shift < 8 {
        h ^= (next >> (shift * 8)) & 0xff;
        h = h.wrapping_mul(0x0000_0100_0000_01b3);
        shift += 1;
    }
    h
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn fnv1a_known_vector() {
        // Well-known FNV-1a 64 vector: fnv1a("") == 0xcbf29ce484222325
        assert_eq!(fnv1a(b""), 0xcbf2_9ce4_8422_2325);
        // fnv1a("a") == 0xaf63dc4c8601ec8c
        assert_eq!(fnv1a(b"a"), 0xaf63_dc4c_8601_ec8c);
    }

    #[test]
    fn parts_combine_is_stable() {
        assert_eq!(fnv1a_parts(&[1, 2, 3]), fnv1a_parts(&[1, 2, 3]));
        assert_ne!(fnv1a_parts(&[1, 2, 3]), fnv1a_parts(&[1, 2, 4]));
    }

    #[test]
    fn str_matches_bytes() {
        assert_eq!(fnv1a_str("hello"), fnv1a(b"hello"));
    }
}