use std::fs::File;
use super::adopt::{allowed_roots, contained};
use super::body::{ModelError, ModelPackageBody, VerifiedModel, verify_model};
use super::locality::Sharing;
use super::registry::{ModelRegistry, ModelTrust};
use crate::mesh_artifact::ArtifactExchange;
use crate::mesh_artifact_types::{ArtifactId, ExchangeError};
use crate::mesh_swarm_state::{Audience, GrantInfo, GrantOrigin, RedistributionPolicy};
use crate::workload_pkg::TrustAnchors;
use crate::workload_pkg::codec::{hex_decode_exact, hex_encode};
pub fn grant_origin(body: &ModelPackageBody) -> GrantOrigin {
if body.redistributable { GrantOrigin::OptIn } else { GrantOrigin::NotFlagged }
}
fn grant_info(package_id: &str, body: &ModelPackageBody, signers: Vec<String>) -> GrantInfo {
GrantInfo {
package_id: package_id.to_string(),
signers,
origin: grant_origin(body),
}
}
pub fn sharing_for(
policy: &dyn RedistributionPolicy,
package_id: &str,
body: &ModelPackageBody,
) -> Sharing {
let grants = [grant_info(package_id, body, Vec::new())];
let all = body.shards.iter().all(|s| {
hex_decode_exact::<32>(&s.blake3)
.is_some_and(|h| policy.allows(&h, &grants, &Audience::Seed))
});
if all { Sharing::Allowed } else { Sharing::Refused }
}
#[derive(Debug, Clone)]
pub struct SeededModel {
pub package_id: String,
pub manifest_hash: String,
pub shards: Vec<(String, ArtifactId)>,
}
#[derive(Debug, thiserror::Error)]
pub enum SeedModelError {
#[error(transparent)]
Model(#[from] ModelError),
#[error("model {0} is not shareable: not opted in, or vetoed by the redistribution policy")]
NotShareable(String),
#[error("model {0} (or its signer or manifest) is revoked")]
Revoked(String),
#[error(transparent)]
Exchange(#[from] ExchangeError),
}
fn signer_keys(verified: &VerifiedModel, anchors: &TrustAnchors) -> Vec<String> {
verified
.signers
.iter()
.filter_map(|s| anchors.signers.iter().find(|k| k.key_id == s.key_id))
.map(|k| hex_encode(&k.public_key))
.collect()
}
pub fn seed_model(
exchange: &ArtifactExchange,
registry: &ModelRegistry,
id_or_name: &str,
anchors: &TrustAnchors,
) -> Result<SeededModel, SeedModelError> {
let resolved = registry.resolve_with(id_or_name, &ModelTrust::new(anchors.clone()))?;
let (_, entry) = registry.get(id_or_name)?;
let verified = verify_model(&entry.envelope, anchors)?;
let package_id = verified.package_id.clone();
let body = &verified.body;
if sharing_for(exchange.config().redistribution.as_ref(), &package_id, body) != Sharing::Allowed {
return Err(SeedModelError::NotShareable(package_id));
}
let signers = signer_keys(&verified, anchors);
let manifest = entry.envelope.to_pretty_json().map_err(ModelError::from)?;
let manifest_hash = *blake3::hash(&manifest).as_bytes();
let shard_hashes = body
.shards
.iter()
.filter_map(|s| hex_decode_exact::<32>(&s.blake3));
for h in std::iter::once(manifest_hash).chain(shard_hashes) {
if exchange.is_revoked_subject(&package_id, &signers, &h) {
return Err(SeedModelError::Revoked(package_id));
}
}
let allowed = allowed_roots(&resolved.root);
let origin = grant_origin(body);
let mut shards = Vec::new();
for (shard, path) in body.shards.iter().zip(&resolved.shards) {
let expect = (
hex_decode_exact::<32>(&shard.blake3).expect("validated hash"),
shard.size,
);
let real = contained(path, &allowed)?;
let mut f = File::open(real).map_err(|e| ModelError::Io {
path: shard.path.clone(),
msg: e.to_string(),
})?;
let d = exchange.seed_reader(&mut f, Some(expect))?;
exchange.grant_with(d.content_hash, &package_id, signers.clone(), origin.clone());
shards.push((shard.path.clone(), d.id()));
}
let m = exchange.seed_bytes(&manifest)?;
exchange.grant_with(m.content_hash, &package_id, signers, origin);
Ok(SeededModel {
package_id,
manifest_hash: hex_encode(&manifest_hash),
shards,
})
}