use serde::{Deserialize, Serialize};
use crate::brand::PHASE;
use crate::Result;
pub const ARCHIVE_FORMAT: &str = "silicera-results-archive";
pub const ARCHIVE_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ArchiveEntry {
pub id: String,
pub captured_at: String,
pub host_brand: String,
pub fingerprint: String,
pub silicera_version: String,
#[serde(default = "default_phase")]
pub phase: String,
pub experiment: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub artifact_path: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hnep_digest: Option<String>,
pub summary: String,
#[serde(default)]
pub highlights: Vec<String>,
}
fn default_phase() -> String {
PHASE.into()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResultsArchive {
pub format: String,
pub version: u32,
pub entries: Vec<ArchiveEntry>,
pub caveats: Vec<String>,
}
impl ResultsArchive {
pub fn empty() -> Self {
Self {
format: ARCHIVE_FORMAT.into(),
version: ARCHIVE_VERSION,
entries: Vec::new(),
caveats: vec![
"Only measured campaigns. Do not invent Machine B or speedups.".into(),
"Fingerprints identify specialization environments — not authentication.".into(),
],
}
}
pub fn load_or_empty(path: &std::path::Path) -> Result<Self> {
if path.is_file() {
let text = std::fs::read_to_string(path)?;
let a: ResultsArchive = serde_json::from_str(&text)?;
Ok(a)
} else {
Ok(Self::empty())
}
}
pub fn push_and_write(&mut self, entry: ArchiveEntry, path: &std::path::Path) -> Result<()> {
self.entries.insert(0, entry);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent)?;
}
std::fs::write(path, serde_json::to_string_pretty(self)?)?;
Ok(())
}
}
pub fn entry_from_eval_json(
path: &std::path::Path,
relative_artifact: &str,
) -> Result<ArchiveEntry> {
let text = std::fs::read_to_string(path)?;
let v: serde_json::Value = serde_json::from_str(&text)?;
let host = v
.get("host_brand")
.and_then(|x| x.as_str())
.unwrap_or("unknown")
.to_string();
let fp = v
.get("fingerprint")
.and_then(|x| x.as_str())
.unwrap_or("")
.to_string();
let ver = v
.get("silicera_version")
.and_then(|x| x.as_str())
.unwrap_or(crate::VERSION)
.to_string();
let summary = v
.get("summary")
.and_then(|x| x.as_str())
.unwrap_or("single-machine-eval")
.to_string();
let calm = v
.pointer("/calm/calm")
.and_then(|x| x.as_bool())
.unwrap_or(false);
let native_wins = v
.pointer("/artifacts/native_win_count")
.and_then(|x| x.as_u64())
.unwrap_or(0);
let portable_wins = v
.pointer("/artifacts/portable_win_count")
.and_then(|x| x.as_u64())
.unwrap_or(0);
let short: String = fp.chars().rev().take(8).collect::<String>().chars().rev().collect();
let id = format!("eval-{}-{short}", chrono::Utc::now().format("%Y%m%d"));
Ok(ArchiveEntry {
id,
captured_at: chrono::Utc::now().to_rfc3339(),
host_brand: host,
fingerprint: fp,
silicera_version: ver,
phase: v
.get("phase")
.and_then(|x| x.as_str())
.unwrap_or(PHASE)
.to_string(),
experiment: "single-machine-eval".into(),
artifact_path: Some(relative_artifact.into()),
hnep_digest: None,
summary,
highlights: vec![
format!("calm={calm}"),
format!("artifact_native_wins={native_wins}"),
format!("artifact_portable_wins={portable_wins}"),
],
})
}