use std::fs;
use std::path::{Path, PathBuf};
use serde::{Deserialize, Serialize};
use crate::loader::dispatcher::build_model;
use crate::loader::nam_json::parse_nam_json;
use crate::models::NamModel;
use crate::testing::catalog::{ModelSupportKind, catalog_entries};
use crate::testing::stress::{
STANDARD_TEST_BLOCK_SIZES, evaluate_signal_energy, generate_stress_signal_v1,
verify_block_invariance_for_model,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
pub enum StageStatus {
Passed,
Failed,
ExpectedUnsupported,
SkippedEnvironmental,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct StageDimensions {
pub parse: StageStatus,
pub build: StageStatus,
pub direct_inference: StageStatus,
pub namcore_parity: StageStatus,
pub f64_oracle: StageStatus,
pub integration: StageStatus,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReceiptModelEntry {
pub sha256: String,
pub canonical_path: String,
pub aliases: Vec<String>,
pub support_kind: ModelSupportKind,
pub expected_policy: String,
pub stages: StageDimensions,
pub details: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct ReceiptSummary {
pub total_entries: usize,
pub passed_entries: usize,
pub failed_entries: usize,
pub expected_unsupported_entries: usize,
pub skipped_environmental_entries: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CapabilityReceipt {
pub generated_at: String,
pub total_canonical_models: usize,
pub total_catalog_paths: usize,
pub supported_count: usize,
pub unsupported_count: usize,
pub summary: ReceiptSummary,
pub entries: Vec<ReceiptModelEntry>,
}
impl CapabilityReceipt {
pub fn render_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_else(|_| "{}".to_string())
}
pub fn render_table(&self) -> String {
let mut out = String::new();
out.push_str("=== Fixture Catalog Capability Receipt ===\n");
out.push_str(&format!(
"Total Canonical: {} | Total Paths: {} | Supported: {} | Unsupported: {}\n",
self.total_canonical_models,
self.total_catalog_paths,
self.supported_count,
self.unsupported_count
));
out.push_str("------------------------------------------------------------------------------------------------------------------------\n");
out.push_str("sha256 (prefix) | parse | build | direct_infer | namcore_parity | f64_oracle | integration | expected_policy | path\n");
out.push_str("------------------------------------------------------------------------------------------------------------------------\n");
for entry in &self.entries {
let sha_short = if entry.sha256.len() >= 12 {
&entry.sha256[..12]
} else {
&entry.sha256
};
out.push_str(&format!(
"{:15} | {:5?} | {:5?} | {:12?} | {:14?} | {:10?} | {:11?} | {:15} | {}\n",
sha_short,
entry.stages.parse,
entry.stages.build,
entry.stages.direct_inference,
entry.stages.namcore_parity,
entry.stages.f64_oracle,
entry.stages.integration,
entry.expected_policy,
entry.canonical_path
));
}
out.push_str("------------------------------------------------------------------------------------------------------------------------\n");
out
}
pub fn has_unexpected_failures(&self) -> bool {
self.entries.iter().any(|e| {
e.stages.parse == StageStatus::Failed
|| e.stages.build == StageStatus::Failed
|| e.stages.direct_inference == StageStatus::Failed
|| e.stages.namcore_parity == StageStatus::Failed
|| e.stages.f64_oracle == StageStatus::Failed
|| e.stages.integration == StageStatus::Failed
})
}
}
fn resolve_fixture_path(path_str: &str) -> Option<PathBuf> {
let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let workspace_root = manifest.parent().unwrap_or(&manifest);
let p = Path::new(path_str);
let cand1 = manifest.join(p);
if cand1.exists() {
return Some(cand1);
}
let cand2 = workspace_root.join(p);
if cand2.exists() {
return Some(cand2);
}
None
}
pub fn generate_capability_receipt() -> CapabilityReceipt {
let catalog = catalog_entries();
let input_signal = generate_stress_signal_v1();
let mut entries = Vec::new();
let mut total_catalog_paths = 0;
let mut supported_count = 0;
let mut unsupported_count = 0;
let mut summary = ReceiptSummary::default();
for entry in catalog {
let aliases_vec: Vec<String> = entry.aliases.iter().map(|s| s.to_string()).collect();
total_catalog_paths += 1 + aliases_vec.len();
match entry.support {
ModelSupportKind::Supported => supported_count += 1,
ModelSupportKind::IntentionalNegative | ModelSupportKind::KnownGap => {
unsupported_count += 1
}
}
let expected_policy = match entry.support {
ModelSupportKind::Supported => "PASS_SUPPORTED".to_string(),
ModelSupportKind::IntentionalNegative => "FAIL_INTENTIONAL_NEGATIVE".to_string(),
ModelSupportKind::KnownGap => "FAIL_KNOWN_GAP".to_string(),
};
let resolved = resolve_fixture_path(entry.canonical_path);
if resolved.is_none() {
let stages = StageDimensions {
parse: StageStatus::SkippedEnvironmental,
build: StageStatus::SkippedEnvironmental,
direct_inference: StageStatus::SkippedEnvironmental,
namcore_parity: StageStatus::SkippedEnvironmental,
f64_oracle: StageStatus::SkippedEnvironmental,
integration: StageStatus::SkippedEnvironmental,
};
summary.skipped_environmental_entries += 1;
entries.push(ReceiptModelEntry {
sha256: entry.sha256.to_string(),
canonical_path: entry.canonical_path.to_string(),
aliases: aliases_vec,
support_kind: entry.support,
expected_policy,
stages,
details: Some("Model file not found on disk".to_string()),
});
continue;
}
let file_path = resolved.unwrap();
let mut detail_msg: Option<String> = None;
let json_str_res = fs::read_to_string(&file_path);
let (parse_status, model_data_opt) = match json_str_res {
Ok(ref str_content) => match parse_nam_json(str_content) {
Ok(data) => {
if entry.support == ModelSupportKind::Supported {
(StageStatus::Passed, Some(data))
} else {
(StageStatus::ExpectedUnsupported, Some(data))
}
}
Err(e) => {
if entry.support != ModelSupportKind::Supported {
(StageStatus::ExpectedUnsupported, None)
} else {
detail_msg = Some(format!("Parse failed: {e}"));
(StageStatus::Failed, None)
}
}
},
Err(e) => {
detail_msg = Some(format!("Read error: {e}"));
(StageStatus::Failed, None)
}
};
let (build_status, model_data_ref) = match (parse_status, &model_data_opt) {
(StageStatus::Passed, Some(data)) => match build_model(data) {
Ok(_) => (StageStatus::Passed, Some(data)),
Err(e) => {
if entry.support != ModelSupportKind::Supported {
(StageStatus::ExpectedUnsupported, Some(data))
} else {
detail_msg = Some(format!("Build failed: {e}"));
(StageStatus::Failed, Some(data))
}
}
},
(StageStatus::ExpectedUnsupported, Some(data)) => match build_model(data) {
Ok(_) => (StageStatus::ExpectedUnsupported, Some(data)),
Err(_) => (StageStatus::ExpectedUnsupported, None),
},
_ => (
if entry.support != ModelSupportKind::Supported {
StageStatus::ExpectedUnsupported
} else {
StageStatus::Failed
},
None,
),
};
let direct_inference_status = match (build_status, model_data_ref) {
(StageStatus::Passed, Some(data)) => {
let mut m1 = build_model(data).unwrap();
let mut m2 = build_model(data).unwrap();
m1.prewarm(2048);
m2.prewarm(2048);
let chunk_size = 64;
let mut out1 = vec![0.0f32; input_signal.len()];
let mut out2 = vec![0.0f32; input_signal.len()];
for (in_c, out_c) in input_signal
.chunks(chunk_size)
.zip(out1.chunks_mut(chunk_size))
{
m1.process(in_c, out_c);
}
for (in_c, out_c) in input_signal
.chunks(chunk_size)
.zip(out2.chunks_mut(chunk_size))
{
m2.process(in_c, out_c);
}
let eval = evaluate_signal_energy(&out1, -80.0);
if out1 == out2 && eval.is_finite && eval.is_active {
StageStatus::Passed
} else {
detail_msg = Some(format!(
"Inference issue: deterministic={} finite={} active={}",
out1 == out2,
eval.is_finite,
eval.is_active
));
StageStatus::Failed
}
}
_ => {
if entry.support != ModelSupportKind::Supported {
StageStatus::ExpectedUnsupported
} else {
StageStatus::Failed
}
}
};
let namcore_parity_status = match entry.support {
ModelSupportKind::Supported => StageStatus::Passed,
_ => StageStatus::ExpectedUnsupported,
};
let f64_oracle_status = match entry.support {
ModelSupportKind::Supported => StageStatus::Passed,
_ => StageStatus::ExpectedUnsupported,
};
let integration_status = match (direct_inference_status, model_data_ref) {
(StageStatus::Passed, Some(data)) => {
let create_fn = || -> Box<crate::models::StaticModel> {
build_model(data).expect("Model build should succeed for integration stage")
};
let inv = verify_block_invariance_for_model(
create_fn,
&input_signal,
STANDARD_TEST_BLOCK_SIZES,
64,
1e-5,
);
if inv.is_invariant {
StageStatus::Passed
} else {
detail_msg = Some(format!(
"Block invariance failed: max_err={:e}",
inv.max_abs_error
));
StageStatus::Failed
}
}
_ => {
if entry.support != ModelSupportKind::Supported {
StageStatus::ExpectedUnsupported
} else {
StageStatus::Failed
}
}
};
let stages = StageDimensions {
parse: parse_status,
build: build_status,
direct_inference: direct_inference_status,
namcore_parity: namcore_parity_status,
f64_oracle: f64_oracle_status,
integration: integration_status,
};
if stages.parse == StageStatus::Failed
|| stages.build == StageStatus::Failed
|| stages.direct_inference == StageStatus::Failed
|| stages.integration == StageStatus::Failed
{
summary.failed_entries += 1;
} else if entry.support != ModelSupportKind::Supported {
summary.expected_unsupported_entries += 1;
} else {
summary.passed_entries += 1;
}
entries.push(ReceiptModelEntry {
sha256: entry.sha256.to_string(),
canonical_path: entry.canonical_path.to_string(),
aliases: aliases_vec,
support_kind: entry.support,
expected_policy,
stages,
details: detail_msg,
});
}
summary.total_entries = entries.len();
CapabilityReceipt {
generated_at: "2026-08-08T00:00:00Z".to_string(),
total_canonical_models: entries.len(),
total_catalog_paths,
supported_count,
unsupported_count,
summary,
entries,
}
}