use std::path::Path;
use std::process::ExitCode;
use calybris_core::digest::{decision_digest, digest_to_hex, input_digest, policy_digest};
use calybris_core::kernel::{KernelModel, PolicySnapshot};
use calybris_core::wal::{
verify_wal, verify_wal_against_anchor, verify_wal_keyed, verify_wal_keyed_against_anchor,
visit_verified_wal, visit_verified_wal_keyed, AuditedRecord, WalAnchor, MIN_HMAC_KEY_BYTES,
};
#[derive(serde::Deserialize)]
struct PolicyArtifact {
policy_epoch: u64,
catalog_epoch: u64,
hard_risk_limit_bps: u16,
minimum_confidence_bps: u16,
risk_penalty_multiplier_bps: u16,
latency_penalty_microunits_per_ms: u64,
models: Vec<KernelModel>,
}
impl PolicyArtifact {
fn into_snapshot(self) -> Result<PolicySnapshot, String> {
PolicySnapshot::try_new(
self.policy_epoch,
self.catalog_epoch,
self.hard_risk_limit_bps,
self.minimum_confidence_bps,
self.risk_penalty_multiplier_bps,
self.latency_penalty_microunits_per_ms,
self.models,
)
.map_err(|error| format!("invalid policy artifact: {error:?}"))
}
}
fn load_policy(path: &str) -> Result<PolicySnapshot, String> {
let raw = std::fs::read_to_string(path)
.map_err(|error| format!("cannot read policy artifact {path}: {error}"))?;
let artifact: PolicyArtifact = serde_json::from_str(&raw)
.map_err(|error| format!("cannot parse policy artifact {path}: {error}"))?;
artifact.into_snapshot()
}
fn load_anchor(path: &str) -> Result<WalAnchor, String> {
let raw = std::fs::read_to_string(path)
.map_err(|error| format!("cannot read WAL anchor {path}: {error}"))?;
serde_json::from_str(&raw).map_err(|error| format!("cannot parse WAL anchor {path}: {error}"))
}
fn parse_hex_key(hex_key: &str) -> Result<Vec<u8>, String> {
let hex_key = hex_key.trim();
if hex_key.len() % 2 != 0 {
return Err("HMAC key hex must have even length".to_string());
}
let mut key = Vec::with_capacity(hex_key.len() / 2);
for (index, pair) in hex_key.as_bytes().chunks_exact(2).enumerate() {
let nibble = |byte: u8| match byte {
b'0'..=b'9' => Some(byte - b'0'),
b'a'..=b'f' => Some(byte - b'a' + 10),
b'A'..=b'F' => Some(byte - b'A' + 10),
_ => None,
};
let high = nibble(pair[0]).ok_or_else(|| format!("invalid hex at offset {}", index * 2))?;
let low =
nibble(pair[1]).ok_or_else(|| format!("invalid hex at offset {}", index * 2 + 1))?;
key.push((high << 4) | low);
}
if key.len() < MIN_HMAC_KEY_BYTES {
return Err(format!(
"HMAC key must be at least {MIN_HMAC_KEY_BYTES} bytes ({} hex chars)",
MIN_HMAC_KEY_BYTES * 2
));
}
Ok(key)
}
struct Args {
command: String,
target: String,
policy: Option<String>,
anchor: Option<String>,
hmac_key: Option<Vec<u8>>,
json: bool,
}
fn parse_args() -> Result<Args, String> {
let mut positional = Vec::new();
let mut policy = None;
let mut anchor = None;
let mut hmac_key = None;
let mut json = false;
let mut iter = std::env::args().skip(1); while let Some(arg) = iter.next() {
match arg.as_str() {
"--policy" => {
policy = Some(iter.next().ok_or("--policy requires a path")?);
}
"--anchor" => {
anchor = Some(iter.next().ok_or("--anchor requires a path")?);
}
"--hmac-key-hex" => {
let raw = iter.next().ok_or("--hmac-key-hex requires a value")?;
hmac_key = Some(parse_hex_key(&raw)?);
}
"--json" => json = true,
"-h" | "--help" => return Err(String::new()),
other => positional.push(other.to_string()),
}
}
if positional.len() != 2 {
return Err(String::new());
}
Ok(Args {
command: positional[0].clone(),
target: positional[1].clone(),
policy,
anchor,
hmac_key,
json,
})
}
fn emit_json(command: &str, ok: bool, detail: &str) {
let escaped = detail.replace('\\', "\\\\").replace('"', "\\\"");
println!("{{\"command\":\"{command}\",\"ok\":{ok},\"detail\":\"{escaped}\"}}");
}
fn usage() {
eprintln!(
"calybris-verify — independent CALY-PROOF v1 auditor\n\n\
USAGE:\n\
\x20 calybris-verify chain <wal.jsonl> [--anchor anchor.json] [--hmac-key-hex HEX]\n\
\x20 calybris-verify audit <wal.jsonl> [--policy policy.json] [--anchor anchor.json] [--hmac-key-hex HEX]\n\
\x20 calybris-verify policy <policy.json>\n\n\
Exit codes: 0 verified, 1 verification failure, 2 usage error.\n\
Spec: docs/CALY_PROOF.md"
);
}
fn cmd_chain(path: &Path, key: Option<&[u8]>, anchor: Option<&WalAnchor>, json: bool) -> ExitCode {
let result = match (key, anchor) {
(Some(key), Some(anchor)) => verify_wal_keyed_against_anchor(path, key, anchor),
(None, Some(anchor)) => verify_wal_against_anchor(path, anchor),
(Some(key), None) => verify_wal_keyed(path, key),
(None, None) => verify_wal(path),
};
match result {
Ok((entries, last_hash)) => {
if json {
emit_json(
"chain",
true,
&format!("{entries} entries, last hash {last_hash}"),
);
} else {
println!("CHAIN OK: {entries} entries, last hash {last_hash}");
}
ExitCode::SUCCESS
}
Err(error) => {
if json {
emit_json("chain", false, &error.to_string());
} else {
eprintln!("CHAIN FAILED: {error}");
}
ExitCode::FAILURE
}
}
}
fn cmd_audit(
path: &Path,
policy: Option<PolicySnapshot>,
key: Option<&[u8]>,
anchor: Option<&WalAnchor>,
json: bool,
) -> ExitCode {
type Record = AuditedRecord<serde_json::Value>;
let expected_policy_hex = policy.as_ref().map(|p| digest_to_hex(&policy_digest(p)));
let mut failures = 0_u64;
let mut entries = 0_u64;
let mut inspect = |entry: calybris_core::wal::WalEntry<Record>| {
entries += 1;
let record = &entry.data;
let mut problems = Vec::new();
if digest_to_hex(&input_digest(&record.input)) != record.audit.input_digest_hex {
problems.push("input digest mismatch");
}
if digest_to_hex(&decision_digest(&record.decision)) != record.audit.decision_digest_hex {
problems.push("decision digest mismatch");
}
if let Some(expected) = &expected_policy_hex {
if &record.audit.policy_digest_hex != expected {
problems.push("policy digest mismatch");
}
}
if let Some(policy) = &policy {
use calybris_core::verify::{verify_decision, VerifyResult};
if verify_decision(policy, record.input, &record.decision) != VerifyResult::Valid {
problems.push("kernel replay mismatch");
}
}
if !problems.is_empty() {
failures += 1;
eprintln!("seq {}: {}", entry.sequence, problems.join(", "));
}
};
let head = match key {
Some(key) => visit_verified_wal_keyed::<Record, _>(path, key, &mut inspect),
None => visit_verified_wal::<Record, _>(path, &mut inspect),
};
let head = match head {
Ok(head) => head,
Err(error) => {
if json {
emit_json("audit", false, &format!("chain/parse: {error}"));
} else {
eprintln!("AUDIT FAILED (chain/parse): {error}");
}
return ExitCode::FAILURE;
}
};
if let Some(anchor) = anchor {
if let Err(error) = anchor.verify_head(head.0, head.1, key.is_some()) {
if json {
emit_json("audit", false, &format!("anchor: {error}"));
} else {
eprintln!("AUDIT FAILED (anchor): {error}");
}
return ExitCode::FAILURE;
}
}
let mode = if policy.is_some() {
"digests + policy + kernel replay"
} else {
"digests only (no policy artifact supplied)"
};
if failures == 0 {
if json {
emit_json(
"audit",
true,
&format!("{entries} entries verified ({mode})"),
);
} else {
println!("AUDIT OK: {entries} entries verified ({mode})");
}
ExitCode::SUCCESS
} else {
if json {
emit_json(
"audit",
false,
&format!("{failures}/{entries} entries failed ({mode})"),
);
} else {
eprintln!("AUDIT FAILED: {failures}/{entries} entries failed ({mode})");
}
ExitCode::FAILURE
}
}
fn cmd_policy(path: &str, json: bool) -> ExitCode {
match load_policy(path) {
Ok(snapshot) => {
let hex = digest_to_hex(&policy_digest(&snapshot));
if json {
emit_json("policy", true, &hex);
} else {
println!("{hex}");
}
ExitCode::SUCCESS
}
Err(error) => {
if json {
emit_json("policy", false, &error.to_string());
} else {
eprintln!("POLICY FAILED: {error}");
}
ExitCode::FAILURE
}
}
}
fn main() -> ExitCode {
let args = match parse_args() {
Ok(args) => args,
Err(message) => {
if !message.is_empty() {
eprintln!("error: {message}\n");
}
usage();
return ExitCode::from(2);
}
};
match args.command.as_str() {
"chain" => {
let anchor = match args.anchor.as_deref().map(load_anchor) {
Some(Ok(anchor)) => Some(anchor),
Some(Err(error)) => {
eprintln!("error: {error}");
return ExitCode::FAILURE;
}
None => None,
};
cmd_chain(
Path::new(&args.target),
args.hmac_key.as_deref(),
anchor.as_ref(),
args.json,
)
}
"audit" => {
let policy = match args.policy.as_deref().map(load_policy) {
Some(Ok(policy)) => Some(policy),
Some(Err(error)) => {
eprintln!("error: {error}");
return ExitCode::FAILURE;
}
None => None,
};
let anchor = match args.anchor.as_deref().map(load_anchor) {
Some(Ok(anchor)) => Some(anchor),
Some(Err(error)) => {
eprintln!("error: {error}");
return ExitCode::FAILURE;
}
None => None,
};
cmd_audit(
Path::new(&args.target),
policy,
args.hmac_key.as_deref(),
anchor.as_ref(),
args.json,
)
}
"policy" => cmd_policy(&args.target, args.json),
_ => {
usage();
ExitCode::from(2)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hmac_key_parser_rejects_unicode_without_panicking() {
let unicode_64_bytes = format!("{}x", "€".repeat(21));
assert!(parse_hex_key(&unicode_64_bytes).is_err());
}
#[test]
fn hmac_key_parser_rejects_short_keys() {
assert!(parse_hex_key("00").is_err());
}
}