use serde::{Deserialize, Serialize};
pub type Digest32 = [u8; 32];
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BackendId(String);
impl BackendId {
pub fn new(id: impl Into<String>) -> Self {
Self(id.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
impl std::fmt::Display for BackendId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(&self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BoundaryPlanHash(pub Digest32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BoundaryReportHash(pub Digest32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AttemptId(pub Digest32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ContentHash(pub Digest32);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct ArtifactId(pub Digest32);
impl ArtifactId {
#[must_use]
pub fn derive(attempt: AttemptId, slot: &str, content: ContentHash) -> Self {
let slot_hash = batpak::event::hash::compute_hash(slot.as_bytes());
let mut framed = [0u8; 96];
framed[..32].copy_from_slice(&attempt.0);
framed[32..64].copy_from_slice(&slot_hash);
framed[64..].copy_from_slice(&content.0);
Self(batpak::event::hash::compute_hash(&framed))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct BackendProfileHash(pub Digest32);
impl BackendProfileHash {
#[must_use]
pub fn of(canonical_bytes: &[u8]) -> Self {
Self(batpak::event::hash::compute_hash(canonical_bytes))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct AdmissionProgramHash(pub Digest32);
#[cfg(test)]
mod id_tests {
use super::{ArtifactId, AttemptId, BackendProfileHash, ContentHash};
#[test]
fn artifact_id_derivation_is_deterministic() {
let attempt = AttemptId([1u8; 32]);
let content = ContentHash([2u8; 32]);
assert_eq!(
ArtifactId::derive(attempt, "out/log", content),
ArtifactId::derive(attempt, "out/log", content),
);
}
#[test]
fn artifact_id_separates_attempt_slot_and_content() {
let attempt = AttemptId([1u8; 32]);
let content = ContentHash([2u8; 32]);
let base = ArtifactId::derive(attempt, "slot", content);
assert_ne!(
base,
ArtifactId::derive(AttemptId([9u8; 32]), "slot", content),
"a different attempt is a different occurrence",
);
assert_ne!(
base,
ArtifactId::derive(attempt, "other", content),
"a different slot is a different occurrence",
);
assert_ne!(
base,
ArtifactId::derive(attempt, "slot", ContentHash([8u8; 32])),
"different content is a different occurrence",
);
}
#[test]
fn identical_bytes_from_different_attempts_are_distinct_artifacts() {
let content = ContentHash([7u8; 32]);
assert_ne!(
ArtifactId::derive(AttemptId([1u8; 32]), "out", content),
ArtifactId::derive(AttemptId([2u8; 32]), "out", content),
);
}
#[test]
fn profile_hash_is_stable_and_distinguishing() {
assert_eq!(
BackendProfileHash::of(b"abc"),
BackendProfileHash::of(b"abc")
);
assert_ne!(
BackendProfileHash::of(b"abc"),
BackendProfileHash::of(b"abd")
);
}
}