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),
#[serde(default, skip_serializing_if = "Option::is_none")]
pub partition_id: Option<String>,
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),
partition_id: Some("plot:0:0".into()),
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);
}
}