use std::path::{Path, PathBuf};
use std::process::Command;
use anyhow::{anyhow, Context, Result};
use super::cache::{cache_model_path, ModelCache, QuantEntry, SourcePointer};
use super::quant_select::{select_quant, GpuInfo, QuantType};
use crate::core::hardware::HardwareProfile;
use crate::core::provenance::{self, compute_source_bundle_sha256, Provenance};
use crate::core::sha256::sha256_file;
use crate::input::integrity::verify_repo;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ModelInput {
Path(PathBuf),
HfRepoId(String),
}
pub fn classify_model_input(arg: &str) -> Result<ModelInput> {
if arg.is_empty() {
return Err(anyhow!("--model is empty"));
}
let p = Path::new(arg);
if p.exists() {
return Ok(ModelInput::Path(p.to_path_buf()));
}
if looks_like_hf_repo_id(arg) {
return Ok(ModelInput::HfRepoId(arg.to_string()));
}
Err(anyhow!(
"--model={arg} does not exist on disk and is not a valid \
HuggingFace repo-id (expected `org/repo-name` with only \
ASCII alphanumerics, '.', '_', '-')"
))
}
pub fn looks_like_hf_repo_id(arg: &str) -> bool {
if arg.is_empty()
|| arg.starts_with('/')
|| arg.starts_with('.')
|| arg.starts_with('\\')
|| arg.contains('\\')
{
return false;
}
let parts: Vec<&str> = arg.split('/').collect();
if parts.len() != 2 {
return false;
}
let valid_part = |s: &str| {
!s.is_empty()
&& s.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '.' || c == '_' || c == '-')
};
valid_part(parts[0]) && valid_part(parts[1])
}
fn map_quant_to_cli(quant: QuantType) -> &'static str {
match quant {
QuantType::Q8_0 => "q8",
QuantType::Q6_K => "q8",
QuantType::Q4_K_M => "q4",
QuantType::Q3_K_M => "q4",
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ResolvedModel {
pub gguf_path: PathBuf,
pub repo_id: Option<String>,
pub quant: Option<QuantType>,
pub from_cache: bool,
}
pub fn resolve_or_prepare_model(
model_arg: &str,
cache: &mut ModelCache,
hw: &HardwareProfile,
no_integrity: bool,
) -> Result<ResolvedModel> {
let input = classify_model_input(model_arg)?;
match input {
ModelInput::Path(p) => Ok(ResolvedModel {
gguf_path: p,
repo_id: None,
quant: None,
from_cache: false,
}),
ModelInput::HfRepoId(repo_id) => run_auto_pipeline(&repo_id, cache, hw, no_integrity),
}
}
fn run_auto_pipeline(
repo_id: &str,
cache: &mut ModelCache,
hw: &HardwareProfile,
no_integrity: bool,
) -> Result<ResolvedModel> {
let info = GpuInfo::from_hardware_profile(hw);
let quant =
select_quant(&info).with_context(|| format!("hardware → quant selection for {repo_id}"))?;
tracing::info!(
repo = repo_id,
memory_gib = info.memory_gib_floor(),
quant = quant.as_str(),
"auto-pipeline: hardware → quant selected"
);
if let Some(hit) = lookup_and_verify(cache, repo_id, quant, no_integrity)? {
cache.touch(repo_id).ok(); return Ok(hit);
}
let _lock = cache
.lock_quant(repo_id, quant)
.with_context(|| format!("acquire cache write lock for {repo_id}@{}", quant.as_str()))?;
if let Some(hit) = lookup_and_verify(cache, repo_id, quant, no_integrity)? {
cache.touch(repo_id).ok();
return Ok(hit);
}
let snapshot = ensure_source_present(cache, repo_id, no_integrity)?;
let target_gguf = cache_model_path(cache.root(), repo_id, quant)?;
if let Some(parent) = target_gguf.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("create quant dir: {}", parent.display()))?;
}
run_convert_subprocess(&snapshot.local_dir, &target_gguf, quant, no_integrity)?;
let bytes = std::fs::metadata(&target_gguf)
.with_context(|| format!("stat produced GGUF: {}", target_gguf.display()))?
.len();
let sha256 = sha256_file(&target_gguf)?;
let entry = QuantEntry {
quant_type: quant.as_str().to_string(),
gguf_path: target_gguf.clone(),
mmproj_path: None,
bytes,
sha256,
quantized_at_secs: secs_since_epoch(),
quantized_by_version: env!("CARGO_PKG_VERSION").to_string(),
};
cache
.record_quantized(repo_id, entry)
.with_context(|| format!("record_quantized for {repo_id}@{}", quant.as_str()))?;
tracing::info!(
repo = repo_id,
quant = quant.as_str(),
path = %target_gguf.display(),
bytes,
"auto-pipeline: cache populated"
);
Ok(ResolvedModel {
gguf_path: target_gguf,
repo_id: Some(repo_id.to_string()),
quant: Some(quant),
from_cache: false,
})
}
fn lookup_and_verify(
cache: &ModelCache,
repo_id: &str,
quant: QuantType,
no_integrity: bool,
) -> Result<Option<ResolvedModel>> {
let entry = match cache.lookup(repo_id, quant) {
Some(e) => e,
None => return Ok(None),
};
let path = entry.gguf_path.clone();
if !path.exists() {
tracing::warn!(
repo = repo_id,
quant = quant.as_str(),
path = %path.display(),
"cache manifest entry references missing GGUF; re-quantizing"
);
return Ok(None);
}
if no_integrity {
tracing::warn!(
repo = repo_id,
quant = quant.as_str(),
"auto-pipeline: --no-integrity set; skipping cached SHA-256 verify (NOT recommended)"
);
} else {
match check_integrity(cache, repo_id, quant, &path)? {
IntegrityOutcome::Pass => {}
IntegrityOutcome::Fail => {
return Ok(None);
}
}
}
tracing::info!(
repo = repo_id,
quant = quant.as_str(),
path = %path.display(),
"auto-pipeline: cache hit"
);
Ok(Some(ResolvedModel {
gguf_path: path,
repo_id: Some(repo_id.to_string()),
quant: Some(quant),
from_cache: true,
}))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum IntegrityOutcome {
Pass,
Fail,
}
fn check_integrity(
cache: &ModelCache,
repo_id: &str,
quant: QuantType,
gguf_path: &Path,
) -> Result<IntegrityOutcome> {
let prov = match mlx_native::gguf::GgufFile::open(gguf_path) {
Ok(g) => provenance::detect(&g),
Err(e) => {
tracing::debug!(
repo = repo_id,
quant = quant.as_str(),
path = %gguf_path.display(),
error = %e,
"auto-pipeline: GGUF header peek failed; falling back to verify_quantized"
);
Provenance::External
}
};
if let Provenance::Hf2q {
producer_version,
source_sha256,
..
} = &prov
{
let cache_bundle_sha = cache
.lookup_model(repo_id)
.and_then(|m| compute_source_bundle_sha256(&m.source_shards));
match cache_bundle_sha {
Some(expected) if expected == *source_sha256 => {
tracing::info!(
repo = repo_id,
quant = quant.as_str(),
producer_version,
"auto-pipeline: hf2q-origin GGUF detected; integrity re-check short-circuited"
);
return Ok(IntegrityOutcome::Pass);
}
Some(expected) => {
return Err(anyhow!(
"hf2q-origin provenance mismatch for {repo}@{quant} at {path}: \
GGUF claims hf2q.source_sha256={claimed}, \
cache shards compute {expected}. \
Either the cached GGUF was tampered with (header keys \
altered while the shard manifest stayed put), the \
source shards under {repo} were re-fetched after the \
GGUF was emitted (so the bundle SHA drifted), or a \
writer/reader version skew is in play. \
Refusing to short-circuit; remove the cached GGUF \
(rm {path}) and re-quantize, or pass --no-integrity \
to skip the check entirely (NOT recommended).",
repo = repo_id,
quant = quant.as_str(),
path = gguf_path.display(),
claimed = source_sha256,
));
}
None => {
tracing::debug!(
repo = repo_id,
quant = quant.as_str(),
"auto-pipeline: hf2q-origin GGUF detected but cache has no \
hashable shards; falling back to verify_quantized"
);
}
}
}
if let Err(e) = cache.verify_quantized(repo_id, quant) {
tracing::warn!(
repo = repo_id,
quant = quant.as_str(),
error = %e,
"cached GGUF failed integrity check; re-quantizing"
);
Ok(IntegrityOutcome::Fail)
} else {
Ok(IntegrityOutcome::Pass)
}
}
struct SnapshotInfo {
local_dir: PathBuf,
}
fn ensure_source_present(
cache: &mut ModelCache,
repo_id: &str,
no_integrity: bool,
) -> Result<SnapshotInfo> {
let detected = ModelCache::detect_hf_hub_source(repo_id);
let (local_dir, revision) = if let Some(snap) = detected {
tracing::info!(
repo = repo_id,
path = %snap.path.display(),
revision = %snap.revision,
"auto-pipeline: hf-hub snapshot already present; skipping download"
);
(snap.path, snap.revision)
} else {
tracing::info!(repo = repo_id, "auto-pipeline: downloading from HF Hub");
let progress = crate::progress::ProgressReporter::new();
let dir = crate::input::hf_download::download_model(repo_id, &progress)
.map_err(|e| anyhow!("HF download for {repo_id}: {e}"))?;
let revision = dir
.file_name()
.and_then(|n| n.to_str())
.filter(|s| s.len() == 40 && s.chars().all(|c| c.is_ascii_hexdigit()))
.unwrap_or("main")
.to_string();
(dir, revision)
};
let source = SourcePointer::HfHub {
path: local_dir.clone(),
revision: revision.clone(),
};
if no_integrity {
tracing::warn!(
repo = repo_id,
"auto-pipeline: --no-integrity set; skipping HF integrity verify (NOT recommended)"
);
cache
.record_source(repo_id, &revision, source)
.with_context(|| format!("record_source for {repo_id}"))?;
} else {
let shards = verify_repo(repo_id, &revision, &local_dir)
.map_err(|e| anyhow!("HF integrity check for {repo_id}@{revision}: {e}"))?;
cache
.record_source_with_shards(repo_id, &revision, source, shards)
.with_context(|| format!("record_source_with_shards for {repo_id}"))?;
}
Ok(SnapshotInfo { local_dir })
}
fn run_convert_subprocess(
snapshot_dir: &Path,
target_gguf: &Path,
quant: QuantType,
no_integrity: bool,
) -> Result<()> {
let bin = std::env::var("CARGO_BIN_EXE_hf2q").unwrap_or_else(|_| {
std::env::current_exe()
.map(|p| p.to_string_lossy().into_owned())
.unwrap_or_else(|_| "hf2q".to_string())
});
let cli_quant = map_quant_to_cli(quant);
if cli_quant_was_degraded(quant) {
tracing::info!(
table_quant = quant.as_str(),
cli_quant,
"auto-pipeline: K-quant emit not yet on CLI (ADR-014 P7); \
degrading to closest available legacy quant"
);
}
let mut cmd = Command::new(&bin);
cmd.arg("convert")
.arg("--input")
.arg(snapshot_dir)
.arg("--format")
.arg("gguf")
.arg("--quant")
.arg(cli_quant)
.arg("--output")
.arg(target_gguf)
.arg("--yes")
.arg("--skip-quality");
if no_integrity {
cmd.arg("--no-integrity");
}
tracing::info!(
bin = %bin,
snapshot = %snapshot_dir.display(),
target = %target_gguf.display(),
cli_quant,
"auto-pipeline: spawning convert subprocess"
);
let started = std::time::Instant::now();
let output = cmd
.output()
.with_context(|| format!("spawn convert subprocess: {bin}"))?;
let elapsed_ms = started.elapsed().as_millis();
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
let stdout = String::from_utf8_lossy(&output.stdout).into_owned();
return Err(anyhow!(
"convert subprocess exited with {} (elapsed {}ms)\n\
--- stdout ---\n{}\n--- stderr ---\n{}",
output.status,
elapsed_ms,
stdout.trim_end(),
stderr.trim_end(),
));
}
if !target_gguf.exists() {
return Err(anyhow!(
"convert subprocess returned 0 but target GGUF is missing at {}",
target_gguf.display()
));
}
tracing::info!(
target = %target_gguf.display(),
elapsed_ms = elapsed_ms as u64,
"auto-pipeline: convert subprocess complete"
);
Ok(())
}
fn cli_quant_was_degraded(quant: QuantType) -> bool {
match quant {
QuantType::Q8_0 => false, QuantType::Q6_K => true, QuantType::Q4_K_M => true, QuantType::Q3_K_M => true, }
}
fn secs_since_epoch() -> u64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_secs())
.unwrap_or(0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_existing_path() {
let m = classify_model_input("/").unwrap();
assert_eq!(m, ModelInput::Path(PathBuf::from("/")));
}
#[test]
fn classify_existing_file_under_tempdir() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("my.gguf");
std::fs::write(&p, b"x").unwrap();
let m = classify_model_input(p.to_str().unwrap()).unwrap();
assert_eq!(m, ModelInput::Path(p));
}
#[test]
fn classify_hf_repo_id_basic() {
let m = classify_model_input("google/gemma-4-27b-it").unwrap();
assert_eq!(m, ModelInput::HfRepoId("google/gemma-4-27b-it".into()));
}
#[test]
fn classify_hf_repo_id_with_dots_and_underscores() {
let m = classify_model_input("Org_1.x/repo-name_v2.0").unwrap();
assert!(matches!(m, ModelInput::HfRepoId(_)));
}
#[test]
fn classify_rejects_nonexistent_absolute_path() {
let err = classify_model_input("/this/does/not/exist.gguf").unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("does not exist") && msg.contains("repo-id"),
"expected guidance in error: {msg}"
);
}
#[test]
fn classify_rejects_nonexistent_relative_path() {
let err = classify_model_input("./missing.gguf").unwrap_err();
let msg = format!("{err}");
assert!(msg.contains("does not exist"), "{msg}");
}
#[test]
fn classify_rejects_multi_slash() {
let err = classify_model_input("org/sub/repo").unwrap_err();
assert!(format!("{err}").contains("repo-id"));
}
#[test]
fn classify_rejects_empty_string() {
let err = classify_model_input("").unwrap_err();
assert!(format!("{err}").contains("empty"));
}
#[test]
fn classify_rejects_no_slash() {
let err = classify_model_input("just-a-name").unwrap_err();
assert!(format!("{err}").contains("repo-id"));
}
#[test]
fn classify_rejects_backslash() {
let err = classify_model_input("org\\repo").unwrap_err();
assert!(format!("{err}").contains("repo-id"));
}
#[test]
fn classify_rejects_special_chars() {
let err = classify_model_input("org/repo with space").unwrap_err();
assert!(format!("{err}").contains("repo-id"));
}
#[test]
fn looks_like_hf_accepts_canonical_shapes() {
assert!(looks_like_hf_repo_id("google/gemma-4-27b-it"));
assert!(looks_like_hf_repo_id("Qwen/Qwen3-MoE-A35B"));
assert!(looks_like_hf_repo_id("a/b"));
assert!(looks_like_hf_repo_id("Org_1.x/repo-name_v2.0"));
}
#[test]
fn looks_like_hf_rejects_paths() {
assert!(!looks_like_hf_repo_id(""));
assert!(!looks_like_hf_repo_id("/abs/path/file"));
assert!(!looks_like_hf_repo_id("./rel"));
assert!(!looks_like_hf_repo_id("../up"));
assert!(!looks_like_hf_repo_id("\\bad\\win"));
assert!(!looks_like_hf_repo_id("org\\repo"));
assert!(!looks_like_hf_repo_id("a/b/c"));
assert!(!looks_like_hf_repo_id("only-org"));
assert!(!looks_like_hf_repo_id("org/"));
assert!(!looks_like_hf_repo_id("/repo"));
assert!(!looks_like_hf_repo_id("org/repo with spaces"));
assert!(!looks_like_hf_repo_id("org/r$pecial"));
}
#[test]
fn map_quant_q8_clean() {
assert_eq!(map_quant_to_cli(QuantType::Q8_0), "q8");
assert!(!cli_quant_was_degraded(QuantType::Q8_0));
}
#[test]
fn map_quant_kquants_degrade_until_p7() {
assert_eq!(map_quant_to_cli(QuantType::Q6_K), "q8");
assert_eq!(map_quant_to_cli(QuantType::Q4_K_M), "q4");
assert_eq!(map_quant_to_cli(QuantType::Q3_K_M), "q4");
assert!(cli_quant_was_degraded(QuantType::Q6_K));
assert!(cli_quant_was_degraded(QuantType::Q4_K_M));
assert!(cli_quant_was_degraded(QuantType::Q3_K_M));
}
#[test]
fn resolve_passthrough_existing_path() {
let dir = tempfile::tempdir().unwrap();
let p = dir.path().join("model.gguf");
std::fs::write(&p, b"x").unwrap();
let cache_dir = dir.path().join("hf2q");
let mut cache = ModelCache::open_at(&cache_dir).unwrap();
let hw = HardwareProfile {
chip_model: "test".into(),
total_memory_bytes: 64u64 << 30,
available_memory_bytes: 64u64 << 30,
total_cores: 16,
performance_cores: 12,
efficiency_cores: 4,
memory_bandwidth_gbs: 400.0,
};
let r = resolve_or_prepare_model(p.to_str().unwrap(), &mut cache, &hw, false).unwrap();
assert_eq!(r.gguf_path, p);
assert_eq!(r.repo_id, None);
assert_eq!(r.quant, None);
assert!(!r.from_cache);
}
#[test]
fn resolve_cache_hit_returns_cached_path_without_network() {
let tmp = tempfile::tempdir().unwrap();
let cache_root = tmp.path().join("hf2q");
let mut cache = ModelCache::open_at(&cache_root).unwrap();
let hw = HardwareProfile {
chip_model: "M5 Max".into(),
total_memory_bytes: 128u64 << 30,
available_memory_bytes: 128u64 << 30,
total_cores: 16,
performance_cores: 12,
efficiency_cores: 4,
memory_bandwidth_gbs: 400.0,
};
let info = GpuInfo::from_hardware_profile(&hw);
let quant = select_quant(&info).unwrap();
assert_eq!(quant, QuantType::Q8_0, "fixture: 128 GiB → Q8_0");
let repo_id = "test-org/test-repo";
cache
.record_source(
repo_id,
"abcdef",
SourcePointer::Local {
path: tmp.path().join("source"),
sha256: "deadbeef".to_string(),
},
)
.unwrap();
let gguf = cache_model_path(cache.root(), repo_id, quant).unwrap();
std::fs::create_dir_all(gguf.parent().unwrap()).unwrap();
std::fs::write(&gguf, b"FAKE GGUF BYTES - only the SHA matters here").unwrap();
let sha = sha256_file(&gguf).unwrap();
let bytes = std::fs::metadata(&gguf).unwrap().len();
cache
.record_quantized(
repo_id,
QuantEntry {
quant_type: quant.as_str().to_string(),
gguf_path: gguf.clone(),
mmproj_path: None,
bytes,
sha256: sha,
quantized_at_secs: secs_since_epoch(),
quantized_by_version: env!("CARGO_PKG_VERSION").to_string(),
},
)
.unwrap();
let r = resolve_or_prepare_model(repo_id, &mut cache, &hw, false).unwrap();
assert!(r.from_cache, "expected cache-hit path");
assert_eq!(r.gguf_path, gguf);
assert_eq!(r.repo_id.as_deref(), Some(repo_id));
assert_eq!(r.quant, Some(QuantType::Q8_0));
}
#[test]
fn lookup_rejects_corrupted_cache_entry() {
let tmp = tempfile::tempdir().unwrap();
let cache_root = tmp.path().join("hf2q");
let mut cache = ModelCache::open_at(&cache_root).unwrap();
let repo_id = "x/y";
let quant = QuantType::Q8_0;
cache
.record_source(
repo_id,
"rev",
SourcePointer::Local {
path: tmp.path().join("src"),
sha256: "n/a".into(),
},
)
.unwrap();
let gguf = cache_model_path(cache.root(), repo_id, quant).unwrap();
std::fs::create_dir_all(gguf.parent().unwrap()).unwrap();
std::fs::write(&gguf, b"original").unwrap();
let real_sha = sha256_file(&gguf).unwrap();
cache
.record_quantized(
repo_id,
QuantEntry {
quant_type: quant.as_str().into(),
gguf_path: gguf.clone(),
mmproj_path: None,
bytes: 8,
sha256: real_sha,
quantized_at_secs: 0,
quantized_by_version: "test".into(),
},
)
.unwrap();
std::fs::write(&gguf, b"CORRUPTED").unwrap();
let hit = lookup_and_verify(&cache, repo_id, quant, false).expect("must not error");
assert!(hit.is_none(), "corrupted cache must fall through");
let hit_unsafe = lookup_and_verify(&cache, repo_id, quant, true).expect("must not error");
assert!(
hit_unsafe.is_some(),
"--no-integrity must skip the SHA check"
);
}
use crate::core::provenance::{compute_source_bundle_sha256, SourceShard};
fn write_str_kv(buf: &mut Vec<u8>, key: &str, value: &str) {
buf.extend_from_slice(&(key.len() as u64).to_le_bytes());
buf.extend_from_slice(key.as_bytes());
buf.extend_from_slice(&8u32.to_le_bytes()); buf.extend_from_slice(&(value.len() as u64).to_le_bytes());
buf.extend_from_slice(value.as_bytes());
}
fn build_gguf_with_string_metadata(pairs: &[(&str, &str)]) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(b"GGUF");
buf.extend_from_slice(&3u32.to_le_bytes()); buf.extend_from_slice(&0u64.to_le_bytes()); buf.extend_from_slice(&(pairs.len() as u64).to_le_bytes()); for (k, v) in pairs {
write_str_kv(&mut buf, k, v);
}
buf
}
fn synthetic_shards() -> Vec<SourceShard> {
vec![
SourceShard {
filename: "model-00001-of-00002.safetensors".into(),
bytes: 100,
sha256: Some("a".repeat(64)),
hf_etag: "a".repeat(64),
is_lfs: true,
verified_at_secs: 1,
},
SourceShard {
filename: "model-00002-of-00002.safetensors".into(),
bytes: 200,
sha256: Some("b".repeat(64)),
hf_etag: "b".repeat(64),
is_lfs: true,
verified_at_secs: 1,
},
SourceShard {
filename: "config.json".into(),
bytes: 1024,
sha256: None, hf_etag: "git-blob-sha".into(),
is_lfs: false,
verified_at_secs: 1,
},
]
}
fn fab_cache_with_provenance(
tmp: &Path,
repo_id: &str,
quant: QuantType,
gguf_bytes: &[u8],
manifest_sha: &str,
shards: Vec<SourceShard>,
) -> (ModelCache, PathBuf) {
let mut cache = ModelCache::open_at(tmp).unwrap();
cache
.record_source(
repo_id,
"rev-iter207",
SourcePointer::Local {
path: tmp.join("source"),
sha256: "n/a".into(),
},
)
.unwrap();
let gguf_path = cache_model_path(cache.root(), repo_id, quant).unwrap();
std::fs::create_dir_all(gguf_path.parent().unwrap()).unwrap();
std::fs::write(&gguf_path, gguf_bytes).unwrap();
cache
.record_quantized(
repo_id,
QuantEntry {
quant_type: quant.as_str().into(),
gguf_path: gguf_path.clone(),
mmproj_path: None,
bytes: gguf_bytes.len() as u64,
sha256: manifest_sha.into(),
quantized_at_secs: 0,
quantized_by_version: "test-iter207".into(),
},
)
.unwrap();
let integ: Vec<crate::core::integrity::ShardIntegrity> = shards
.iter()
.map(|s| crate::core::integrity::ShardIntegrity {
filename: s.filename.clone(),
bytes: s.bytes,
sha256: s.sha256.clone(),
hf_etag: s.hf_etag.clone(),
is_lfs: s.is_lfs,
})
.collect();
cache
.record_source_with_shards(
repo_id,
"rev-iter207",
SourcePointer::Local {
path: tmp.join("source"),
sha256: "n/a".into(),
},
integ,
)
.unwrap();
(cache, gguf_path)
}
#[test]
fn auto_pipeline_short_circuits_on_hf2q_provenance_match() {
let tmp = tempfile::tempdir().unwrap();
let repo_id = "iter207/short-circuit";
let quant = QuantType::Q8_0;
let shards = synthetic_shards();
let bundle_sha = compute_source_bundle_sha256(&shards)
.expect("synthetic shards must produce a bundle SHA");
let gguf_bytes = build_gguf_with_string_metadata(&[
("hf2q.producer_version", "hf2q 0.1.0-test"),
("hf2q.source_sha256", &bundle_sha),
]);
let bogus_manifest_sha = "0".repeat(64);
let (cache, gguf_path) = fab_cache_with_provenance(
tmp.path(),
repo_id,
quant,
&gguf_bytes,
&bogus_manifest_sha,
shards,
);
let result = lookup_and_verify(&cache, repo_id, quant, false)
.expect("short-circuit must produce Ok, not Err");
let hit = result.expect("short-circuit must produce Some(hit)");
assert_eq!(hit.gguf_path, gguf_path);
assert!(hit.from_cache);
assert_eq!(hit.repo_id.as_deref(), Some(repo_id));
assert_eq!(hit.quant, Some(quant));
}
#[test]
fn auto_pipeline_falls_back_to_verify_when_external() {
let tmp = tempfile::tempdir().unwrap();
let repo_id = "iter207/external-pass";
let quant = QuantType::Q8_0;
let gguf_bytes = build_gguf_with_string_metadata(&[
("general.architecture", "qwen35"),
("general.name", "test"),
]);
let real_sha = {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(&gguf_bytes);
hex::encode(h.finalize())
};
let (cache, _) = fab_cache_with_provenance(
tmp.path(),
repo_id,
quant,
&gguf_bytes,
&real_sha,
synthetic_shards(),
);
let result = lookup_and_verify(&cache, repo_id, quant, false).expect("verify path must Ok");
let hit = result.expect("matching SHA must produce Some(hit)");
assert!(hit.from_cache);
}
#[test]
fn auto_pipeline_falls_through_when_external_and_verify_fails() {
let tmp = tempfile::tempdir().unwrap();
let repo_id = "iter207/external-fail";
let quant = QuantType::Q8_0;
let gguf_bytes = build_gguf_with_string_metadata(&[("general.architecture", "qwen35")]);
let bogus_sha = "f".repeat(64);
let (cache, _) = fab_cache_with_provenance(
tmp.path(),
repo_id,
quant,
&gguf_bytes,
&bogus_sha,
synthetic_shards(),
);
let result = lookup_and_verify(&cache, repo_id, quant, false)
.expect("verify-fail must NOT error (only mismatch errors)");
assert!(
result.is_none(),
"external GGUF + bad manifest SHA must fall through to None"
);
}
#[test]
fn auto_pipeline_errors_on_hf2q_provenance_mismatch() {
let tmp = tempfile::tempdir().unwrap();
let repo_id = "iter207/provenance-mismatch";
let quant = QuantType::Q8_0;
let shards = synthetic_shards();
let _real_bundle_sha = compute_source_bundle_sha256(&shards).unwrap();
let claimed_bundle_sha = "9".repeat(64);
let gguf_bytes = build_gguf_with_string_metadata(&[
("hf2q.producer_version", "hf2q 0.1.0-test"),
("hf2q.source_sha256", &claimed_bundle_sha),
]);
let real_sha = {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(&gguf_bytes);
hex::encode(h.finalize())
};
let (cache, gguf_path) =
fab_cache_with_provenance(tmp.path(), repo_id, quant, &gguf_bytes, &real_sha, shards);
let err = lookup_and_verify(&cache, repo_id, quant, false)
.expect_err("provenance mismatch must Err, not silently re-quantize");
let msg = format!("{err}");
assert!(
msg.contains("provenance mismatch"),
"error must name the mismatch; got: {msg}"
);
assert!(
msg.contains(&claimed_bundle_sha),
"error must surface the claimed SHA so an operator can diagnose; got: {msg}"
);
assert!(
msg.contains(&gguf_path.display().to_string()),
"error must surface the cached GGUF path; got: {msg}"
);
}
#[test]
fn auto_pipeline_falls_back_when_hf2q_keys_present_but_no_cache_shards() {
let tmp = tempfile::tempdir().unwrap();
let repo_id = "iter207/no-shards";
let quant = QuantType::Q8_0;
let gguf_bytes = build_gguf_with_string_metadata(&[
("hf2q.producer_version", "hf2q 0.1.0"),
("hf2q.source_sha256", &"7".repeat(64)),
]);
let real_sha = {
use sha2::{Digest, Sha256};
let mut h = Sha256::new();
h.update(&gguf_bytes);
hex::encode(h.finalize())
};
let mut cache = ModelCache::open_at(tmp.path()).unwrap();
cache
.record_source(
repo_id,
"rev",
SourcePointer::Local {
path: tmp.path().join("source"),
sha256: "n/a".into(),
},
)
.unwrap();
let gguf_path = cache_model_path(cache.root(), repo_id, quant).unwrap();
std::fs::create_dir_all(gguf_path.parent().unwrap()).unwrap();
std::fs::write(&gguf_path, &gguf_bytes).unwrap();
cache
.record_quantized(
repo_id,
QuantEntry {
quant_type: quant.as_str().into(),
gguf_path: gguf_path.clone(),
mmproj_path: None,
bytes: gguf_bytes.len() as u64,
sha256: real_sha,
quantized_at_secs: 0,
quantized_by_version: "test".into(),
},
)
.unwrap();
let result = lookup_and_verify(&cache, repo_id, quant, false).expect("ok");
let hit = result.expect("verify-quantized passes → Some(hit)");
assert_eq!(hit.gguf_path, gguf_path);
}
}