clawft_kernel/model_manifest/
sharing.rs1use std::fs::File;
17
18use super::adopt::{allowed_roots, contained};
19use super::body::{ModelError, ModelPackageBody, VerifiedModel, verify_model};
20use super::locality::Sharing;
21use super::registry::{ModelRegistry, ModelTrust};
22use crate::mesh_artifact::ArtifactExchange;
23use crate::mesh_artifact_types::{ArtifactId, ExchangeError};
24use crate::mesh_swarm_state::{Audience, GrantInfo, GrantOrigin, RedistributionPolicy};
25use crate::workload_pkg::TrustAnchors;
26use crate::workload_pkg::codec::{hex_decode_exact, hex_encode};
27
28pub fn grant_origin(body: &ModelPackageBody) -> GrantOrigin {
30 if body.redistributable { GrantOrigin::OptIn } else { GrantOrigin::NotFlagged }
31}
32
33fn grant_info(package_id: &str, body: &ModelPackageBody, signers: Vec<String>) -> GrantInfo {
34 GrantInfo {
35 package_id: package_id.to_string(),
36 signers,
37 origin: grant_origin(body),
38 }
39}
40
41pub fn sharing_for(
44 policy: &dyn RedistributionPolicy,
45 package_id: &str,
46 body: &ModelPackageBody,
47) -> Sharing {
48 let grants = [grant_info(package_id, body, Vec::new())];
49 let all = body.shards.iter().all(|s| {
50 hex_decode_exact::<32>(&s.blake3)
51 .is_some_and(|h| policy.allows(&h, &grants, &Audience::Seed))
52 });
53 if all { Sharing::Allowed } else { Sharing::Refused }
54}
55
56#[derive(Debug, Clone)]
58pub struct SeededModel {
59 pub package_id: String,
61 pub manifest_hash: String,
63 pub shards: Vec<(String, ArtifactId)>,
65}
66
67#[derive(Debug, thiserror::Error)]
69pub enum SeedModelError {
70 #[error(transparent)]
72 Model(#[from] ModelError),
73 #[error("model {0} is not shareable: not opted in, or vetoed by the redistribution policy")]
75 NotShareable(String),
76 #[error("model {0} (or its signer or manifest) is revoked")]
78 Revoked(String),
79 #[error(transparent)]
81 Exchange(#[from] ExchangeError),
82}
83
84fn signer_keys(verified: &VerifiedModel, anchors: &TrustAnchors) -> Vec<String> {
85 verified
86 .signers
87 .iter()
88 .filter_map(|s| anchors.signers.iter().find(|k| k.key_id == s.key_id))
89 .map(|k| hex_encode(&k.public_key))
90 .collect()
91}
92
93pub fn seed_model(
102 exchange: &ArtifactExchange,
103 registry: &ModelRegistry,
104 id_or_name: &str,
105 anchors: &TrustAnchors,
106) -> Result<SeededModel, SeedModelError> {
107 let resolved = registry.resolve_with(id_or_name, &ModelTrust::new(anchors.clone()))?;
108 let (_, entry) = registry.get(id_or_name)?;
109 let verified = verify_model(&entry.envelope, anchors)?;
110 let package_id = verified.package_id.clone();
111 let body = &verified.body;
112 if sharing_for(exchange.config().redistribution.as_ref(), &package_id, body) != Sharing::Allowed {
113 return Err(SeedModelError::NotShareable(package_id));
114 }
115 let signers = signer_keys(&verified, anchors);
116 let manifest = entry.envelope.to_pretty_json().map_err(ModelError::from)?;
117 let manifest_hash = *blake3::hash(&manifest).as_bytes();
118 let shard_hashes = body
119 .shards
120 .iter()
121 .filter_map(|s| hex_decode_exact::<32>(&s.blake3));
122 for h in std::iter::once(manifest_hash).chain(shard_hashes) {
123 if exchange.is_revoked_subject(&package_id, &signers, &h) {
124 return Err(SeedModelError::Revoked(package_id));
125 }
126 }
127 let allowed = allowed_roots(&resolved.root);
128 let origin = grant_origin(body);
129 let mut shards = Vec::new();
130 for (shard, path) in body.shards.iter().zip(&resolved.shards) {
131 let expect = (
132 hex_decode_exact::<32>(&shard.blake3).expect("validated hash"),
133 shard.size,
134 );
135 let real = contained(path, &allowed)?;
138 let mut f = File::open(real).map_err(|e| ModelError::Io {
139 path: shard.path.clone(),
140 msg: e.to_string(),
141 })?;
142 let d = exchange.seed_reader(&mut f, Some(expect))?;
143 exchange.grant_with(d.content_hash, &package_id, signers.clone(), origin.clone());
144 shards.push((shard.path.clone(), d.id()));
145 }
146 let m = exchange.seed_bytes(&manifest)?;
147 exchange.grant_with(m.content_hash, &package_id, signers, origin);
148 Ok(SeededModel {
149 package_id,
150 manifest_hash: hex_encode(&manifest_hash),
151 shards,
152 })
153}