use anyhow::{Result, anyhow};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use auths_keri::{Event, TrustedKel, parse_kel_json, verify_event_said};
use auths_sdk::keri::KelResolverChain;
use auths_sdk::storage::{GitRegistryBackend, RegistryConfig};
use crate::config::CliConfig;
use crate::ux::format::is_json_mode;
#[derive(Parser, Debug, Clone)]
#[command(
about = "Inspect a Key Event Log (read-only)",
after_help = "Examples:
auths kel validate # validate your own identity's local KEL
auths kel validate --did did:keri:... # validate another identity's local KEL
auths kel validate --kel kel.json # validate a KEL JSON file"
)]
pub struct KelCommand {
#[command(subcommand)]
pub command: KelSubcommand,
}
#[derive(Subcommand, Debug, Clone)]
pub enum KelSubcommand {
Validate(KelValidateArgs),
}
#[derive(Parser, Debug, Clone)]
pub struct KelValidateArgs {
#[arg(long, value_name = "KEL.json")]
pub kel: Option<PathBuf>,
#[arg(long, value_name = "did:keri:...", conflicts_with = "kel")]
pub did: Option<String>,
}
impl KelCommand {
pub fn execute(&self, ctx: &CliConfig) -> Result<()> {
match &self.command {
KelSubcommand::Validate(args) => validate(args, ctx),
}
}
}
fn validate(args: &KelValidateArgs, ctx: &CliConfig) -> Result<()> {
let (source, events) = load_events(args, ctx)?;
if events.is_empty() {
return Err(anyhow!("{source} contains no key events"));
}
for (idx, event) in events.iter().enumerate() {
if let Err(e) = verify_event_said(event) {
return Err(anyhow!(
"{source}: stale or invalid encoding at event {idx}: {e}"
));
}
}
if let Err(e) = TrustedKel::from_trusted_source(&events).replay() {
return Err(anyhow!("{source}: broken chain: {e}"));
}
report_ok(&source, events.len());
Ok(())
}
fn report_ok(source: &str, count: usize) {
if is_json_mode() {
let out = serde_json::json!({
"valid": true,
"source": source,
"events": count,
});
println!("{out}");
} else {
println!("OK: {source} — {count} event(s), SAIDs and chain linkage valid");
}
}
fn load_events(args: &KelValidateArgs, ctx: &CliConfig) -> Result<(String, Vec<Event>)> {
if let Some(path) = &args.kel {
let json = std::fs::read_to_string(path)
.map_err(|e| anyhow!("cannot read {}: {e}", path.display()))?;
let events = parse_kel_json(&json).map_err(|e| anyhow!("cannot parse KEL JSON: {e}"))?;
return Ok((path.display().to_string(), events));
}
let auths_home = auths_sdk::storage_layout::resolve_repo_path(ctx.repo_path.clone())?;
let did = match &args.did {
Some(d) => d.clone(),
None => {
let sdk_ctx =
crate::factories::storage::build_auths_context(&auths_home, &ctx.env_config, None)?;
auths_sdk::workflows::commit_trust::local_self_root(&sdk_ctx).ok_or_else(|| {
anyhow!("no local identity found — run `auths init` first, or pass --kel/--did")
})?
}
};
let registry =
GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(&auths_home));
let events = KelResolverChain::local(®istry)
.resolve_kel(&did)
.map_err(|e| anyhow!("cannot resolve local KEL for {did}: {e}"))?;
Ok((did, events))
}