use serde::{Deserialize, Serialize};
use crate::digest_compat::Digest;
use crate::ids_compat::AgentId;
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: Digest,
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,
#[cfg(feature = "typed-ids")]
#[serde(skip_serializing_if = "Option::is_none")]
pub scope: Option<stack_ids::ScopeKey>,
}
impl PoolManifest {
#[allow(clippy::too_many_arguments)]
pub fn new(
pool_id: Digest,
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,
#[cfg(feature = "typed-ids")]
scope: None,
};
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.hex().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<Digest> {
crate::digest_compat::compute_json(self)
}
#[cfg(feature = "typed-ids")]
pub fn with_scope(mut self, scope: stack_ids::ScopeKey) -> Self {
self.scope = Some(scope);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ShellManifest {
pub schema_version: String,
pub agent_id: AgentId,
pub pool_digest: Digest,
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: AgentId,
pool_digest: Digest,
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.hex().is_empty() {
return Err(crate::error::PolyKvError::InvalidManifest(
"pool_digest is empty".into(),
));
}
Ok(())
}
pub fn digest(&self) -> crate::error::Result<Digest> {
crate::digest_compat::compute_json(self)
}
}
const CODEC_IDENTIFIER_SHARED: &str = crate::policy::CODEC_FIB_K4_N32;
const CODEC_IDENTIFIER_SHELL: &str = crate::policy::CODEC_TURBO_8BIT;