auths_cli/commands/
kel.rs1use anyhow::{Result, anyhow};
9use clap::{Parser, Subcommand};
10use std::path::PathBuf;
11
12use auths_keri::{Event, TrustedKel, parse_kel_json, verify_event_said};
13use auths_sdk::keri::KelResolverChain;
14use auths_sdk::storage::{GitRegistryBackend, RegistryConfig};
15
16use crate::config::CliConfig;
17use crate::ux::format::is_json_mode;
18
19#[derive(Parser, Debug, Clone)]
21#[command(
22 about = "Inspect a Key Event Log (read-only)",
23 after_help = "Examples:
24 auths kel validate # validate your own identity's local KEL
25 auths kel validate --did did:keri:... # validate another identity's local KEL
26 auths kel validate --kel kel.json # validate a KEL JSON file"
27)]
28pub struct KelCommand {
29 #[command(subcommand)]
30 pub command: KelSubcommand,
31}
32
33#[derive(Subcommand, Debug, Clone)]
35pub enum KelSubcommand {
36 Validate(KelValidateArgs),
41}
42
43#[derive(Parser, Debug, Clone)]
45pub struct KelValidateArgs {
46 #[arg(long, value_name = "KEL.json")]
48 pub kel: Option<PathBuf>,
49
50 #[arg(long, value_name = "did:keri:...", conflicts_with = "kel")]
52 pub did: Option<String>,
53}
54
55impl KelCommand {
56 pub fn execute(&self, ctx: &CliConfig) -> Result<()> {
58 match &self.command {
59 KelSubcommand::Validate(args) => validate(args, ctx),
60 }
61 }
62}
63
64fn validate(args: &KelValidateArgs, ctx: &CliConfig) -> Result<()> {
66 let (source, events) = load_events(args, ctx)?;
67 if events.is_empty() {
68 return Err(anyhow!("{source} contains no key events"));
69 }
70
71 for (idx, event) in events.iter().enumerate() {
75 if let Err(e) = verify_event_said(event) {
76 return Err(anyhow!(
77 "{source}: stale or invalid encoding at event {idx}: {e}"
78 ));
79 }
80 }
81
82 if let Err(e) = TrustedKel::from_trusted_source(&events).replay() {
85 return Err(anyhow!("{source}: broken chain: {e}"));
86 }
87
88 report_ok(&source, events.len());
89 Ok(())
90}
91
92fn report_ok(source: &str, count: usize) {
94 if is_json_mode() {
95 let out = serde_json::json!({
96 "valid": true,
97 "source": source,
98 "events": count,
99 });
100 println!("{out}");
101 } else {
102 println!("OK: {source} — {count} event(s), SAIDs and chain linkage valid");
103 }
104}
105
106fn load_events(args: &KelValidateArgs, ctx: &CliConfig) -> Result<(String, Vec<Event>)> {
108 if let Some(path) = &args.kel {
109 let json = std::fs::read_to_string(path)
110 .map_err(|e| anyhow!("cannot read {}: {e}", path.display()))?;
111 let events = parse_kel_json(&json).map_err(|e| anyhow!("cannot parse KEL JSON: {e}"))?;
112 return Ok((path.display().to_string(), events));
113 }
114
115 let auths_home = auths_sdk::storage_layout::resolve_repo_path(ctx.repo_path.clone())?;
116 let did = match &args.did {
117 Some(d) => d.clone(),
118 None => {
119 let sdk_ctx =
120 crate::factories::storage::build_auths_context(&auths_home, &ctx.env_config, None)?;
121 auths_sdk::workflows::commit_trust::local_self_root(&sdk_ctx).ok_or_else(|| {
122 anyhow!("no local identity found — run `auths init` first, or pass --kel/--did")
123 })?
124 }
125 };
126 let registry =
127 GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(&auths_home));
128 let events = KelResolverChain::local(®istry)
129 .resolve_kel(&did)
130 .map_err(|e| anyhow!("cannot resolve local KEL for {did}: {e}"))?;
131 Ok((did, events))
132}