use serde::{Deserialize, Serialize};
use crate::policy::{CodecId, CompressionPolicy};
use crate::shape::KvTensorShape;
pub const POOL_MANIFEST_SCHEMA: &str = "pool_manifest_v1";
pub const SHELL_MANIFEST_SCHEMA: &str = "shell_manifest_v1";
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PoolManifest {
pub schema_version: String,
pub pool_id: String,
pub shape: KvTensorShape,
pub policy: CompressionPolicy,
pub num_shared_tokens: u32,
pub num_layers: u32,
pub pool_size_bytes: u64,
pub shared_codec: CodecId,
pub compression_ratio: f64,
pub built_at_unix: i64,
pub build_seed: u64,
}
impl PoolManifest {
#[allow(clippy::too_many_arguments)]
pub fn new(
pool_id: String,
shape: KvTensorShape,
policy: CompressionPolicy,
num_shared_tokens: u32,
num_layers: u32,
pool_size_bytes: u64,
raw_size_bytes: u64,
build_seed: u64,
built_at_unix: i64,
) -> crate::error::Result<Self> {
let compression_ratio = if pool_size_bytes > 0 {
raw_size_bytes as f64 / pool_size_bytes as f64
} else {
0.0
};
let manifest = Self {
schema_version: POOL_MANIFEST_SCHEMA.into(),
pool_id,
shape,
policy,
num_shared_tokens,
num_layers,
pool_size_bytes,
shared_codec: CODEC_IDENTIFIER_SHARED.into(),
compression_ratio,
built_at_unix,
build_seed,
};
manifest.validate()?;
Ok(manifest)
}
pub fn validate(&self) -> crate::error::Result<()> {
if self.schema_version != POOL_MANIFEST_SCHEMA {
return Err(crate::error::PolyKvError::InvalidManifest(format!(
"expected schema {}, got {}",
POOL_MANIFEST_SCHEMA, self.schema_version
)));
}
if self.pool_id.is_empty() {
return Err(crate::error::PolyKvError::InvalidManifest(
"pool_id is empty".into(),
));
}
self.shape.validate()?;
self.policy.validate()?;
Ok(())
}
pub fn digest(&self) -> crate::error::Result<String> {
let json = serde_json::to_string(self)?;
Ok(blake3::hash(json.as_bytes()).to_hex().to_string())
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShellManifest {
pub schema_version: String,
pub agent_id: String,
pub pool_digest: String,
pub num_unique_tokens: u32,
pub num_unique_layers: u32,
pub shell_size_bytes: u64,
pub shell_codec: CodecId,
pub materialized_at_unix: i64,
pub materialize_seed: u64,
}
impl ShellManifest {
pub fn new(
agent_id: String,
pool_digest: String,
num_unique_tokens: u32,
num_unique_layers: u32,
shell_size_bytes: u64,
materialize_seed: u64,
materialized_at_unix: i64,
) -> crate::error::Result<Self> {
let manifest = Self {
schema_version: SHELL_MANIFEST_SCHEMA.into(),
agent_id,
pool_digest,
num_unique_tokens,
num_unique_layers,
shell_size_bytes,
shell_codec: CODEC_IDENTIFIER_SHELL.into(),
materialized_at_unix,
materialize_seed,
};
manifest.validate()?;
Ok(manifest)
}
pub fn validate(&self) -> crate::error::Result<()> {
if self.schema_version != SHELL_MANIFEST_SCHEMA {
return Err(crate::error::PolyKvError::InvalidManifest(format!(
"expected schema {}, got {}",
SHELL_MANIFEST_SCHEMA, self.schema_version
)));
}
if self.agent_id.is_empty() {
return Err(crate::error::PolyKvError::InvalidManifest(
"agent_id is empty".into(),
));
}
if self.pool_digest.is_empty() {
return Err(crate::error::PolyKvError::InvalidManifest(
"pool_digest is empty".into(),
));
}
Ok(())
}
pub fn digest(&self) -> crate::error::Result<String> {
let json = serde_json::to_string(self)?;
Ok(blake3::hash(json.as_bytes()).to_hex().to_string())
}
}
const CODEC_IDENTIFIER_SHARED: &str = crate::policy::CODEC_FIB_K4_N32;
const CODEC_IDENTIFIER_SHELL: &str = crate::policy::CODEC_TURBO_8BIT;