use serde::{Deserialize, Serialize};
use std::fmt;
use crate::world_segment::ids::{ClusterId, ContentId};
use crate::world_segment::score::Tier;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Serialize, Deserialize)]
pub struct StableBuildId(pub ContentId);
impl StableBuildId {
pub fn seed(source_id: &str, snapshot_build: ClusterId) -> Self {
StableBuildId(ContentId::of(&[
b"stable.v1",
source_id.as_bytes(),
snapshot_build.0.as_bytes(),
]))
}
}
impl fmt::Display for StableBuildId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)]
pub struct Provenance {
pub stable_build_id: StableBuildId,
pub snapshot_build_id: ClusterId,
pub source_id: String,
pub snapshot_id: String,
pub world_bbox: ((i32, i32, i32), (i32, i32, i32)),
pub origin_offset: (i32, i32, i32),
pub block_count: u64,
pub cluster_count: u32,
pub fingerprint: u128,
pub tier: Tier,
pub config_hash: ContentId,
pub profile_hash: ContentId,
pub extracted_at: i64,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::world_segment::ids::{ClusterId, ContentId, TileId};
fn cid() -> ClusterId {
ClusterId::new(ContentId::of(&[b"c"]), TileId { x: 0, z: 0 }, None, (0,0,0))
}
#[test]
fn seed_is_deterministic_and_source_scoped() {
let a = StableBuildId::seed("world_a", cid());
let b = StableBuildId::seed("world_a", cid());
let c = StableBuildId::seed("world_b", cid());
assert_eq!(a, b, "same source + build → same id");
assert_ne!(a, c, "different source → different id");
}
#[test]
fn provenance_round_trips_through_serde() {
let p = Provenance {
stable_build_id: StableBuildId::seed("w", cid()),
snapshot_build_id: cid(),
source_id: "w".into(),
snapshot_id: "2026-05-19".into(),
world_bbox: ((0,0,0),(9,9,9)),
origin_offset: (0,0,0),
block_count: 100,
cluster_count: 1,
fingerprint: 42,
tier: crate::world_segment::score::Tier::Confident,
config_hash: ContentId::of(&[b"cfg"]),
profile_hash: ContentId::of(&[b"prof"]),
extracted_at: 1_700_000_000,
};
let bytes = serde_json::to_vec(&p).unwrap();
let back: Provenance = serde_json::from_slice(&bytes).unwrap();
assert_eq!(p, back);
}
}