use chrono::Utc;
use clap::Parser;
use serde::Serialize;
use sha2::{Digest, Sha256};
use std::{
env, fs, io,
path::{Path, PathBuf},
};
#[derive(Parser, Debug)]
#[command(
name = "vaultseal_register",
about = "Produce a VaultSeal JSON for a run TS"
)]
struct Args {
#[arg(long)]
ts: Option<String>,
#[arg(long)]
input: Option<PathBuf>,
#[arg(long)]
out: Option<PathBuf>,
}
#[derive(Serialize)]
struct AgentSeal {
agent: String,
file_path: String,
sha256: String,
}
#[derive(Serialize)]
struct VaultSeal {
ts: String,
generated_at: String,
agents: Vec<AgentSeal>,
combined_sha256: String,
}
fn read_ts_from_args_or_runfile(
arg_ts: Option<String>,
) -> Result<String, Box<dyn std::error::Error>> {
if let Some(ts) = arg_ts {
return Ok(ts);
}
if let Some(ts_arg) = env::args().find(|a| a.starts_with("--ts=")) {
return Ok(ts_arg.trim_start_matches("--ts=").to_string());
}
let ts = fs::read_to_string("vault_dev_log/RUN_TS")?
.trim()
.to_string();
Ok(ts)
}
fn load_agent_file(
agent: &str,
ts: &str,
) -> Result<(PathBuf, Vec<u8>, String), Box<dyn std::error::Error>> {
let flat = PathBuf::from(format!("vault_dev_log/{agent}_{ts}.json"));
let nested = PathBuf::from(format!("vault_dev_log/{agent}/{ts}.json"));
let path = if flat.exists() {
flat
} else if nested.exists() {
nested
} else {
return Err(format!(
"Missing agent log for '{agent}': expected vault_dev_log/{agent}_{ts}.json or vault_dev_log/{agent}/{ts}.json"
)
.into());
};
let data = fs::read(&path)?;
let mut h = Sha256::new();
h.update(&data);
let file_hash = hex::encode(h.finalize());
Ok((path, data, file_hash))
}
fn hash_file(path: &Path) -> Result<(Vec<u8>, String), Box<dyn std::error::Error>> {
let data = fs::read(path)?;
let mut h = Sha256::new();
h.update(&data);
Ok((data.clone(), hex::encode(h.finalize())))
}
fn safe_write_file(path: &Path, bytes: &[u8]) -> io::Result<()> {
if path.is_dir() {
return Err(io::Error::new(
io::ErrorKind::AlreadyExists,
"Target is a directory",
));
}
if let Some(parent) = path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(path, bytes)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = Args::parse();
let ts = read_ts_from_args_or_runfile(args.ts)?;
let agents = [
"Naorix",
"EchoLoom",
"SealMind",
"WhisperGen",
"VaultCrafter",
"REEMSmith",
"Scribe-001",
"Overseer-7",
];
let mut per_agent: Vec<AgentSeal> = Vec::new();
let mut combined_hasher = Sha256::new();
let mut found_any = false;
for agent in agents {
match load_agent_file(agent, &ts) {
Ok((path, data, file_hash)) => {
found_any = true;
combined_hasher.update(&data);
per_agent.push(AgentSeal {
agent: agent.to_string(),
file_path: path.to_string_lossy().into_owned(),
sha256: file_hash,
});
}
Err(e) => eprintln!("(warn) {e}"),
}
}
if let Some(input_path) = args.input.as_ref() {
let (data, file_hash) = hash_file(input_path)?;
combined_hasher.update(&data);
per_agent.push(AgentSeal {
agent: "ManualInput".to_string(),
file_path: input_path.to_string_lossy().into_owned(),
sha256: file_hash,
});
found_any = true;
}
if !found_any {
return Err("No agent logs found and no --input provided; nothing to seal.".into());
}
let seal = VaultSeal {
ts: ts.clone(),
generated_at: Utc::now().to_rfc3339(),
agents: per_agent,
combined_sha256: hex::encode(combined_hasher.finalize()),
};
let seal_json = serde_json::to_string_pretty(&seal)?;
let seal_dir = PathBuf::from(".vaultseal");
fs::create_dir_all(&seal_dir)?;
let out_path = seal_dir.join(format!("VaultSeal_{ts}.json"));
safe_write_file(&out_path, seal_json.as_bytes())
.map_err(|e| format!("writing {} failed: {}", out_path.display(), e))?;
safe_write_file(&seal_dir.join("LATEST"), format!("{ts}\n").as_bytes())
.map_err(|e| format!("writing .vaultseal/LATEST failed: {e}"))?;
let audit_path = PathBuf::from(format!("vault_dev_log/VaultSeal_{ts}.json"));
match safe_write_file(&audit_path, seal_json.as_bytes()) {
Ok(_) => {}
Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {
eprintln!(
"(audit) {} is a directory; skipping audit copy",
audit_path.display()
);
}
Err(e) => return Err(format!("writing {} failed: {}", audit_path.display(), e).into()),
}
if let Some(user_out) = args.out.as_ref() {
if let Some(parent) = user_out.parent() {
fs::create_dir_all(parent)?;
}
fs::write(user_out, &seal_json)?;
println!("π Also wrote: {}", user_out.display());
}
println!("β
VaultSeal created");
println!(" β’ {}", out_path.display());
println!(" β’ {}", audit_path.display());
println!("π Combined SHA256: {}", seal.combined_sha256);
Ok(())
}