use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::path::PathBuf;
use std::sync::Arc;
use dashmap::DashMap;
use tensor_wasm_artifacts::{ArtifactError, ArtifactStore, ContentHash, DiskArtifactStore};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[non_exhaustive]
pub struct KernelManifest {
pub name: String,
pub version: String,
pub sm_version: u32,
pub digest: [u8; 32],
pub signature: [u8; 32],
pub published_unix_ms: u64,
pub publisher: String,
#[serde(default)]
pub launch_geometry: Option<(u32, u32)>,
}
impl KernelManifest {
pub fn new(
name: String,
version: String,
sm_version: u32,
digest: [u8; 32],
signature: [u8; 32],
published_unix_ms: u64,
publisher: String,
) -> Self {
Self {
name,
version,
sm_version,
digest,
signature,
published_unix_ms,
publisher,
launch_geometry: None,
}
}
pub fn with_launch_geometry(mut self, launch_geometry: Option<(u32, u32)>) -> Self {
self.launch_geometry = launch_geometry;
self
}
pub fn digest_as_u64(&self) -> u64 {
let mut buf = [0u8; 8];
buf.copy_from_slice(&self.digest[..8]);
u64::from_le_bytes(buf)
}
pub(crate) fn canonical_signed_bytes(&self) -> Vec<u8> {
let cap = 12
+ 6 * 8
+ self.name.len()
+ self.version.len()
+ self.publisher.len()
+ 8
+ 4
+ self.digest.len();
let mut buf = Vec::with_capacity(cap);
buf.extend_from_slice(SIGNED_BYTES_MAGIC_V2);
push_len_prefixed(&mut buf, self.name.as_bytes());
push_len_prefixed(&mut buf, self.version.as_bytes());
push_len_prefixed(&mut buf, self.publisher.as_bytes());
push_len_prefixed(&mut buf, &self.published_unix_ms.to_le_bytes());
push_len_prefixed(&mut buf, &self.sm_version.to_le_bytes());
push_len_prefixed(&mut buf, &self.digest);
buf
}
}
pub(crate) const SIGNED_BYTES_MAGIC_V2: &[u8; 12] = b"twasm-kmf-v2";
fn push_len_prefixed(buf: &mut Vec<u8>, bytes: &[u8]) {
buf.extend_from_slice(&(bytes.len() as u64).to_le_bytes());
buf.extend_from_slice(bytes);
}
pub trait BlueprintResolver: Send + Sync {
fn resolve(&self, blueprint_fp: u64, sm_version: u32) -> Option<(String, String)>;
}
pub struct InMemoryBlueprintResolver {
map: HashMap<(u64, u32), (String, String)>,
}
impl InMemoryBlueprintResolver {
pub fn new() -> Self {
Self {
map: HashMap::new(),
}
}
pub fn from_map(map: HashMap<(u64, u32), (String, String)>) -> Self {
Self { map }
}
pub fn insert(&mut self, blueprint_fp: u64, sm_version: u32, name: String, version: String) {
self.map.insert((blueprint_fp, sm_version), (name, version));
}
}
impl Default for InMemoryBlueprintResolver {
fn default() -> Self {
Self::new()
}
}
impl BlueprintResolver for InMemoryBlueprintResolver {
fn resolve(&self, blueprint_fp: u64, sm_version: u32) -> Option<(String, String)> {
self.map.get(&(blueprint_fp, sm_version)).cloned()
}
}
#[derive(Debug, thiserror::Error)]
pub enum RegistryError {
#[error("kernel not found: {0}")]
NotFound(String),
#[error("signature verification failed for {0}")]
BadSignature(String),
#[error("digest mismatch for {0}")]
DigestMismatch(String),
#[error("name @ version already registered: {0}")]
AlreadyRegistered(String),
#[error("publisher not allowlisted for {0}")]
PublisherNotAllowed(String),
#[error("artifact store error: {0}")]
Storage(String),
#[error("manifest codec error: {0}")]
Codec(String),
}
pub trait KernelRegistry: Send + Sync {
fn get(
&self,
name: &str,
version: &str,
) -> Result<Arc<(KernelManifest, String)>, RegistryError>;
fn publish(&self, manifest: KernelManifest, ptx_text: String) -> Result<(), RegistryError>;
fn list(&self) -> Vec<KernelManifest>;
fn list_paginated(&self, offset: usize, limit: usize) -> Vec<KernelManifest> {
let limit = limit.min(DISK_REGISTRY_MAX_LIMIT);
self.list().into_iter().skip(offset).take(limit).collect()
}
}
pub struct InMemoryRegistry {
entries: parking_lot::Mutex<HashMap<String, Arc<(KernelManifest, String)>>>,
hmac_key: zeroize::Zeroizing<[u8; 32]>,
}
impl InMemoryRegistry {
pub fn new(hmac_key: [u8; 32]) -> Self {
Self {
entries: parking_lot::Mutex::new(HashMap::new()),
hmac_key: zeroize::Zeroizing::new(hmac_key),
}
}
pub fn publish(&self, manifest: KernelManifest, ptx_text: String) -> Result<(), RegistryError> {
let actual = blake3::hash(ptx_text.as_bytes());
if actual.as_bytes() != &manifest.digest {
return Err(RegistryError::DigestMismatch(manifest.name.clone()));
}
self.verify_signature(&manifest)?;
let key = format!("{}@{}", manifest.name, manifest.version);
let mut entries = self.entries.lock();
if entries.contains_key(&key) {
return Err(RegistryError::AlreadyRegistered(key));
}
entries.insert(key, Arc::new((manifest, ptx_text)));
Ok(())
}
fn verify_signature(&self, manifest: &KernelManifest) -> Result<(), RegistryError> {
verify_manifest_signature(manifest, &self.hmac_key)
}
}
fn verify_manifest_signature(
manifest: &KernelManifest,
hmac_key: &[u8; 32],
) -> Result<(), RegistryError> {
use hmac::{Hmac, Mac};
let mut mac = <Hmac<sha2::Sha256> as Mac>::new_from_slice(&hmac_key[..])
.expect("32-byte key is always valid HMAC-SHA256 input");
mac.update(&manifest.canonical_signed_bytes());
let expected = mac.finalize().into_bytes();
let ok = subtle::ConstantTimeEq::ct_eq(&expected[..], &manifest.signature[..]);
if bool::from(ok) {
Ok(())
} else {
Err(RegistryError::BadSignature(manifest.name.clone()))
}
}
impl KernelRegistry for InMemoryRegistry {
fn get(
&self,
name: &str,
version: &str,
) -> Result<Arc<(KernelManifest, String)>, RegistryError> {
let key = format!("{name}@{version}");
self.entries
.lock()
.get(&key)
.cloned()
.ok_or(RegistryError::NotFound(key))
}
fn publish(&self, manifest: KernelManifest, ptx_text: String) -> Result<(), RegistryError> {
InMemoryRegistry::publish(self, manifest, ptx_text)
}
fn list(&self) -> Vec<KernelManifest> {
self.entries.lock().values().map(|e| e.0.clone()).collect()
}
}
pub fn sign_manifest(unsigned: &KernelManifest, hmac_key: &[u8; 32]) -> [u8; 32] {
use hmac::{Hmac, Mac};
let mut mac = <Hmac<sha2::Sha256> as Mac>::new_from_slice(hmac_key)
.expect("32-byte key is always valid HMAC-SHA256 input");
mac.update(&unsigned.canonical_signed_bytes());
mac.finalize().into_bytes().into()
}
pub const DISK_REGISTRY_DEFAULT_LIMIT: usize = 100;
pub const DISK_REGISTRY_MAX_LIMIT: usize = 1000;
const DISK_REGISTRY_DEFAULT_LIST: (usize, usize) = (0, DISK_REGISTRY_DEFAULT_LIMIT);
type ManifestBlob = (KernelManifest, String);
#[derive(Clone)]
struct KeymapEntry {
hash: ContentHash,
manifest: KernelManifest,
}
pub struct DiskRegistry {
artifact_store: DiskArtifactStore,
keymap: Arc<DashMap<(String, String, u32), KeymapEntry>>,
sm_index: Arc<DashMap<(String, String), Vec<u32>>>,
hmac_key: zeroize::Zeroizing<[u8; 32]>,
publisher_allowlist: Option<HashSet<String>>,
}
fn index_sm_version(
index: &DashMap<(String, String), Vec<u32>>,
name: String,
version: String,
sm: u32,
) {
let mut entry = index.entry((name, version)).or_default();
match entry.binary_search(&sm) {
Ok(_) => {}
Err(pos) => entry.insert(pos, sm),
}
}
impl DiskRegistry {
pub fn open(dir: PathBuf, hmac_key: [u8; 32]) -> Result<Self, RegistryError> {
let artifact_store = DiskArtifactStore::new(dir, hmac_key);
let keymap: Arc<DashMap<(String, String, u32), KeymapEntry>> = Arc::new(DashMap::new());
let sm_index: Arc<DashMap<(String, String), Vec<u32>>> = Arc::new(DashMap::new());
let hashes = artifact_store.list().map_err(|e| {
tracing::warn!(
target: "tensor_wasm_jit::registry",
error = %e,
"restart-recovery: artifact store list failed; refusing to boot with an \
unknown-completeness keymap"
);
RegistryError::Storage(e.to_string())
})?;
for hash in hashes {
let raw = match artifact_store.get(&hash) {
Ok(bytes) => bytes,
Err(e) => {
tracing::warn!(
target: "tensor_wasm_jit::registry",
hash = %hash,
error = %e,
"restart-recovery: artifact store read failed; skipping blob"
);
continue;
}
};
let (manifest, _ptx) = match decode_manifest_blob(&raw) {
Ok(pair) => pair,
Err(e) => {
tracing::warn!(
target: "tensor_wasm_jit::registry",
hash = %hash,
error = %e,
"restart-recovery: bincode decode failed; skipping blob"
);
continue;
}
};
if let Err(e) = verify_manifest_signature(&manifest, &hmac_key) {
tracing::warn!(
target: "tensor_wasm_jit::registry",
name = manifest.name.as_str(),
version = manifest.version.as_str(),
error = %e,
"restart-recovery: manifest signature does not verify under \
current hmac_key; skipping blob (registry key may have rotated)"
);
continue;
}
let k = (
manifest.name.clone(),
manifest.version.clone(),
manifest.sm_version,
);
let (name, version, sm) = (k.0.clone(), k.1.clone(), k.2);
keymap.insert(k, KeymapEntry { hash, manifest });
index_sm_version(&sm_index, name, version, sm);
}
Ok(Self {
artifact_store,
keymap,
sm_index,
hmac_key: zeroize::Zeroizing::new(hmac_key),
publisher_allowlist: None,
})
}
pub fn with_publisher_allowlist(mut self, allowlist: HashSet<String>) -> Self {
self.publisher_allowlist = Some(allowlist);
self
}
fn verify_signature(&self, manifest: &KernelManifest) -> Result<(), RegistryError> {
verify_manifest_signature(manifest, &self.hmac_key)
}
pub fn publish(&self, manifest: KernelManifest, ptx_text: String) -> Result<(), RegistryError> {
if let Some(allow) = &self.publisher_allowlist {
if !allow.contains(&manifest.publisher) {
return Err(RegistryError::PublisherNotAllowed(manifest.name.clone()));
}
}
let actual = blake3::hash(ptx_text.as_bytes());
if actual.as_bytes() != &manifest.digest {
return Err(RegistryError::DigestMismatch(manifest.name.clone()));
}
self.verify_signature(&manifest)?;
let key = (
manifest.name.clone(),
manifest.version.clone(),
manifest.sm_version,
);
if self.keymap.contains_key(&key) {
return Err(RegistryError::AlreadyRegistered(format!(
"{}@{} (sm_{})",
manifest.name, manifest.version, manifest.sm_version,
)));
}
let cached_manifest = manifest.clone();
let blob: ManifestBlob = (manifest, ptx_text);
let bytes = encode_manifest_blob(&blob)?;
let hash = self
.artifact_store
.put(&bytes)
.map_err(|e| RegistryError::Storage(e.to_string()))?;
let (name, version, sm) = (key.0.clone(), key.1.clone(), key.2);
self.keymap.insert(
key,
KeymapEntry {
hash,
manifest: cached_manifest,
},
);
index_sm_version(&self.sm_index, name, version, sm);
Ok(())
}
pub fn list_paginated(&self, offset: usize, limit: usize) -> Vec<KernelManifest> {
let limit = limit.min(DISK_REGISTRY_MAX_LIMIT);
let mut out: Vec<KernelManifest> = Vec::with_capacity(limit);
for (i, kv) in self.keymap.iter().enumerate() {
if i < offset {
continue;
}
if out.len() >= limit {
break;
}
out.push(kv.value().manifest.clone());
}
out
}
}
fn encode_manifest_blob(blob: &ManifestBlob) -> Result<Vec<u8>, RegistryError> {
bincode::serde::encode_to_vec(blob, bincode::config::legacy())
.map_err(|e| RegistryError::Codec(e.to_string()))
}
fn decode_manifest_blob(bytes: &[u8]) -> Result<ManifestBlob, RegistryError> {
let (blob, _consumed): (ManifestBlob, usize) =
bincode::serde::decode_from_slice(bytes, bincode::config::legacy())
.map_err(|e| RegistryError::Codec(e.to_string()))?;
Ok(blob)
}
impl KernelRegistry for DiskRegistry {
fn get(
&self,
name: &str,
version: &str,
) -> Result<Arc<(KernelManifest, String)>, RegistryError> {
let best_sm: Option<u32> = self
.sm_index
.get(&(name.to_string(), version.to_string()))
.and_then(|entry| entry.value().last().copied());
let hit: Option<ContentHash> = best_sm.and_then(|sm| {
self.keymap
.get(&(name.to_string(), version.to_string(), sm))
.map(|kv| kv.value().hash)
});
let hash = match hit {
Some(h) => h,
None => {
return Err(RegistryError::NotFound(format!("{name}@{version}")));
}
};
let raw = self.artifact_store.get(&hash).map_err(|e| match e {
ArtifactError::NotFound(_) => {
RegistryError::NotFound(format!("{name}@{version}"))
}
other => RegistryError::Storage(other.to_string()),
})?;
let (manifest, ptx_text) = decode_manifest_blob(&raw)?;
Ok(Arc::new((manifest, ptx_text)))
}
fn publish(&self, manifest: KernelManifest, ptx_text: String) -> Result<(), RegistryError> {
DiskRegistry::publish(self, manifest, ptx_text)
}
fn list(&self) -> Vec<KernelManifest> {
let (offset, limit) = DISK_REGISTRY_DEFAULT_LIST;
DiskRegistry::list_paginated(self, offset, limit)
}
fn list_paginated(&self, offset: usize, limit: usize) -> Vec<KernelManifest> {
DiskRegistry::list_paginated(self, offset, limit)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn signed_manifest(
name: &str,
version: &str,
ptx_text: &str,
key: &[u8; 32],
) -> KernelManifest {
let digest = *blake3::hash(ptx_text.as_bytes()).as_bytes();
let mut m = KernelManifest::new(
name.to_string(),
version.to_string(),
80,
digest,
[0u8; 32],
0,
"test".to_string(),
);
m.signature = sign_manifest(&m, key);
m
}
fn signed_manifest_sm(
name: &str,
version: &str,
sm_version: u32,
ptx_text: &str,
key: &[u8; 32],
) -> KernelManifest {
let digest = *blake3::hash(ptx_text.as_bytes()).as_bytes();
let mut m = KernelManifest::new(
name.to_string(),
version.to_string(),
sm_version,
digest,
[0u8; 32],
0,
"test".to_string(),
);
m.signature = sign_manifest(&m, key);
m
}
#[test]
fn disk_get_resolves_highest_sm_version() {
let key = [0x42u8; 32];
let tmp = tempfile::TempDir::new().expect("tempdir");
let reg = DiskRegistry::open(tmp.path().to_path_buf(), key).expect("open");
for sm in [70u32, 90, 80] {
let ptx = format!("// ptx for sm_{sm}\n");
let m = signed_manifest_sm("matmul.f32", "1.0.0", sm, &ptx, &key);
KernelRegistry::publish(®, m, ptx).expect("publish");
}
let got = KernelRegistry::get(®, "matmul.f32", "1.0.0").expect("get");
assert_eq!(
got.0.sm_version, 90,
"get must resolve the highest sm_version"
);
assert_eq!(got.1, "// ptx for sm_90\n");
}
#[test]
fn disk_sm_index_survives_restart() {
let key = [0x37u8; 32];
let tmp = tempfile::TempDir::new().expect("tempdir");
{
let reg = DiskRegistry::open(tmp.path().to_path_buf(), key).expect("open");
for sm in [75u32, 86, 80] {
let ptx = format!("// ptx for sm_{sm}\n");
let m = signed_manifest_sm("conv.f32", "2.1.0", sm, &ptx, &key);
KernelRegistry::publish(®, m, ptx).expect("publish");
}
}
let reopened = DiskRegistry::open(tmp.path().to_path_buf(), key).expect("reopen");
let got = KernelRegistry::get(&reopened, "conv.f32", "2.1.0").expect("get after restart");
assert_eq!(
got.0.sm_version, 86,
"reopened registry must still resolve the highest sm_version via the rebuilt index"
);
assert_eq!(got.1, "// ptx for sm_86\n");
assert!(matches!(
KernelRegistry::get(&reopened, "conv.f32", "9.9.9"),
Err(RegistryError::NotFound(_))
));
}
#[test]
fn publish_and_get_roundtrip() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
reg.publish(m.clone(), ptx.clone()).unwrap();
let got = reg.get("matmul.f32", "1.0.0").unwrap();
assert_eq!(got.0.name, "matmul.f32");
assert_eq!(got.1, ptx);
let listing = reg.list();
assert_eq!(listing.len(), 1);
assert_eq!(listing[0].version, "1.0.0");
}
#[test]
fn rejects_bad_signature() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let mut m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
m.signature[0] ^= 0xff;
match reg.publish(m, ptx) {
Err(RegistryError::BadSignature(name)) => assert_eq!(name, "matmul.f32"),
other => panic!("expected BadSignature, got {other:?}"),
}
}
#[test]
fn rejects_digest_mismatch() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
match reg.publish(m, "// different ptx\n".to_string()) {
Err(RegistryError::DigestMismatch(name)) => assert_eq!(name, "matmul.f32"),
other => panic!("expected DigestMismatch, got {other:?}"),
}
}
#[test]
fn rejects_duplicate_publish() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
reg.publish(m.clone(), ptx.clone()).unwrap();
match reg.publish(m, ptx) {
Err(RegistryError::AlreadyRegistered(key)) => {
assert_eq!(key, "matmul.f32@1.0.0")
}
other => panic!("expected AlreadyRegistered, got {other:?}"),
}
}
#[test]
fn get_returns_not_found_for_missing() {
let key = [0u8; 32];
let reg = InMemoryRegistry::new(key);
match reg.get("nope", "0.0.0") {
Err(RegistryError::NotFound(k)) => assert_eq!(k, "nope@0.0.0"),
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn v2_envelope_roundtrip_verifies() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
reg.verify_signature(&m)
.expect("freshly signed manifest must verify");
}
#[test]
fn v2_envelope_rejects_publisher_tamper() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let mut m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
reg.verify_signature(&m).expect("baseline must verify");
m.publisher = "attacker".to_string();
match reg.verify_signature(&m) {
Err(RegistryError::BadSignature(name)) => assert_eq!(name, "matmul.f32"),
other => panic!("expected BadSignature after publisher tamper, got {other:?}"),
}
}
#[test]
fn v2_envelope_rejects_timestamp_tamper() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let mut m = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
reg.verify_signature(&m).expect("baseline must verify");
m.published_unix_ms = m.published_unix_ms.wrapping_add(1);
match reg.verify_signature(&m) {
Err(RegistryError::BadSignature(name)) => assert_eq!(name, "matmul.f32"),
other => panic!("expected BadSignature after timestamp tamper, got {other:?}"),
}
}
#[test]
fn v2_envelope_avoids_nul_collision() {
let digest = [0u8; 32];
let a = KernelManifest::new(
"a\0b".to_string(),
"c".to_string(),
80,
digest,
[0u8; 32],
0,
"p".to_string(),
);
let b = KernelManifest::new(
"a".to_string(),
"b\0c".to_string(),
80,
digest,
[0u8; 32],
0,
"p".to_string(),
);
let ca = a.canonical_signed_bytes();
let cb = b.canonical_signed_bytes();
assert_ne!(
ca, cb,
"v2 canonical envelope MUST disambiguate name/version field boundaries"
);
let key = [0u8; 32];
let sig_a = sign_manifest(&a, &key);
let sig_b = sign_manifest(&b, &key);
assert_ne!(
sig_a, sig_b,
"v2 signatures MUST differ across NUL-collision pair"
);
}
#[test]
fn v2_envelope_rejects_legacy_v1_signature() {
use hmac::{Hmac, Mac};
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n";
let digest = *blake3::hash(ptx.as_bytes()).as_bytes();
let mut m = KernelManifest::new(
"matmul.f32".to_string(),
"1.0.0".to_string(),
80,
digest,
[0u8; 32],
12345,
"legacy-publisher".to_string(),
);
let mut mac = <Hmac<sha2::Sha256> as Mac>::new_from_slice(&key)
.expect("32-byte key is always valid HMAC-SHA256 input");
mac.update(m.name.as_bytes());
mac.update(b"\0");
mac.update(m.version.as_bytes());
mac.update(b"\0");
mac.update(&m.sm_version.to_le_bytes());
mac.update(&m.digest);
m.signature = mac.finalize().into_bytes().into();
match reg.verify_signature(&m) {
Err(RegistryError::BadSignature(name)) => assert_eq!(name, "matmul.f32"),
other => panic!("v1-shaped signature MUST NOT verify under v2 MAC: {other:?}"),
}
}
#[test]
fn launch_geometry_defaults_none_and_builder_sets_it() {
let digest = [0u8; 32];
let m = KernelManifest::new(
"matmul.f32".to_string(),
"1.0.0".to_string(),
80,
digest,
[0u8; 32],
0,
"p".to_string(),
);
assert_eq!(
m.launch_geometry, None,
"new() must default geometry to None"
);
let m = m.with_launch_geometry(Some((8, 128)));
assert_eq!(m.launch_geometry, Some((8, 128)));
assert_eq!(m.with_launch_geometry(None).launch_geometry, None);
}
#[test]
fn launch_geometry_is_unsigned_and_does_not_affect_mac() {
let key = [0x42u8; 32];
let reg = InMemoryRegistry::new(key);
let ptx = "// fake ptx\n".to_string();
let signed = signed_manifest("matmul.f32", "1.0.0", &ptx, &key);
let bytes_before = signed.canonical_signed_bytes();
let with_geo = signed.with_launch_geometry(Some((8, 128)));
let bytes_after = with_geo.canonical_signed_bytes();
assert_eq!(
bytes_before, bytes_after,
"launch_geometry must not alter the canonical signed bytes"
);
reg.verify_signature(&with_geo)
.expect("manifest must still verify after attaching unsigned geometry hint");
}
#[test]
fn launch_geometry_round_trips_and_old_blobs_default_none() {
let key = [0x42u8; 32];
let ptx = "// fake ptx\n".to_string();
let m =
signed_manifest("matmul.f32", "1.0.0", &ptx, &key).with_launch_geometry(Some((4, 256)));
let blob: ManifestBlob = (m.clone(), ptx.clone());
let encoded = encode_manifest_blob(&blob).expect("encode");
let (decoded, decoded_ptx) = decode_manifest_blob(&encoded).expect("decode");
assert_eq!(decoded.launch_geometry, Some((4, 256)));
assert_eq!(decoded_ptx, ptx);
verify_manifest_signature(&decoded, &key).expect("decoded manifest must verify");
let old = signed_manifest("conv2d.f32", "2.0.0", &ptx, &key);
assert_eq!(old.launch_geometry, None);
let old_blob: ManifestBlob = (old, ptx.clone());
let old_encoded = encode_manifest_blob(&old_blob).expect("encode old");
let (old_decoded, _) = decode_manifest_blob(&old_encoded).expect("decode old");
assert_eq!(
old_decoded.launch_geometry, None,
"geometry-less blob must deserialize to None"
);
verify_manifest_signature(&old_decoded, &key)
.expect("geometry-less (old-shape) manifest must still verify");
}
}