haz-cache 0.2.0

Content-addressed cache for haz task outputs using BLAKE3.
Documentation
//! Schema-version prefix (`CACHE-003`).
//!
//! Every cache key incorporates a two-byte prefix before its other
//! components: a `chapter_revision` byte and a `hash_function_id`
//! byte. The two bytes are kept distinct so the cross-product of
//! chapter revisions and hash functions yields a unique prefix for
//! every pair. Combining them via XOR or addition before hashing is
//! a normative violation (`CACHE-003`).

use haz_domain::settings::cache::HashAlgo;

/// Revision of the cache-key composition rules in
/// `docs/spec/09-caching.md`.
///
/// Bumped only on a normative change to `CACHE-004`..`CACHE-009`.
/// Bumping invalidates every prior cache entry by construction:
/// the prefix changes and no current lookup re-derives the same
/// key for content already stored.
pub const CHAPTER_REVISION: u8 = 0;

/// The single byte identifying `algo` in the cache-key prefix,
/// matching the registry in `CACHE-002`.
///
/// The registry is append-only: a new value MUST receive a
/// previously-unused id.
#[must_use]
pub fn hash_function_id(algo: HashAlgo) -> u8 {
    match algo {
        HashAlgo::Blake3 => 0x00,
        HashAlgo::Sha256 => 0x01,
    }
}

/// The two-byte schema-version prefix for `algo`.
///
/// Layout: `[CHAPTER_REVISION, hash_function_id(algo)]`. Always
/// fed into the hasher first, ahead of every other component.
#[must_use]
pub fn schema_version_prefix(algo: HashAlgo) -> [u8; 2] {
    [CHAPTER_REVISION, hash_function_id(algo)]
}

#[cfg(test)]
mod tests {
    use haz_domain::settings::cache::HashAlgo;

    use crate::key::prefix::{CHAPTER_REVISION, hash_function_id, schema_version_prefix};

    #[test]
    fn cache_003_initial_chapter_revision_is_zero() {
        assert_eq!(CHAPTER_REVISION, 0);
    }

    #[test]
    fn cache_002_blake3_hash_function_id_is_zero() {
        assert_eq!(hash_function_id(HashAlgo::Blake3), 0x00);
    }

    #[test]
    fn cache_002_sha256_hash_function_id_is_one() {
        assert_eq!(hash_function_id(HashAlgo::Sha256), 0x01);
    }

    #[test]
    fn cache_003_prefix_keeps_the_two_bytes_distinct() {
        let blake = schema_version_prefix(HashAlgo::Blake3);
        let sha = schema_version_prefix(HashAlgo::Sha256);
        assert_eq!(blake, [0, 0]);
        assert_eq!(sha, [0, 1]);
        // Re-asserts CACHE-003: the bytes are NOT collapsed into a
        // single byte by XOR/addition.
        assert_ne!(blake[1], sha[1]);
        assert_eq!(blake[0], sha[0]);
    }
}