use crate::load::load_entries;
use candor_classify::policy::{parse_policy, rule_and_upgrade, unverified_hole_rule, PolicyRule};
use candor_report::ReportEntry;
pub(crate) fn cmd_unverified(args: &[String]) -> i32 {
if args.is_empty() {
eprintln!("usage: candor-query unverified <prefix> [policy] [--strict] [0|1]");
return 2;
}
let prefix = &args[0];
let mut policy_path: Option<String> = None;
let mut want_json = false;
let mut strict = false;
for a in &args[1..] {
match a.as_str() {
"0" => want_json = false,
"1" | "--json" => want_json = true,
"--strict" => strict = true,
other => policy_path = Some(other.to_string()),
}
}
let policy_path = policy_path.or_else(|| std::env::var("CANDOR_POLICY").ok());
let Some(pp) = policy_path else {
eprintln!("candor unverified: a policy is required (the check is relative to your pure/deny layers).");
return 2;
};
let rules = match std::fs::read_to_string(&pp) {
Ok(t) => parse_policy(&t).rules,
Err(e) => {
eprintln!("candor: policy `{pp}` could not be read ({e}) — nothing checked.");
return 2;
}
};
let entries = load_entries(prefix);
if entries.is_empty() {
eprintln!("candor unverified: no report for `{prefix}` — scan the crate first.");
return 2;
}
struct Hole<'a> {
func: &'a ReportEntry,
rule: &'a PolicyRule,
}
let holes: Vec<Hole> = entries
.iter()
.filter_map(|e| {
unverified_hole_rule(&e.func, &e.inferred, &rules).map(|rule| Hole { func: e, rule })
})
.collect();
if want_json {
let items: Vec<_> = holes
.iter()
.map(|h| {
let (rule, upgrade) = rule_and_upgrade(h.rule);
serde_json::json!({
"fn": h.func.func,
"rule": rule,
"unknownWhy": h.func.unknown_why,
"upgrade": upgrade,
})
})
.collect();
let out = serde_json::json!({ "ok": items.is_empty(), "unverified": items });
println!("{}", serde_json::to_string_pretty(&out).unwrap());
return if strict && !holes.is_empty() { 1 } else { 0 };
}
if holes.is_empty() {
println!("candor unverified: every function in a pure/deny layer is PROVABLY clean (no Unknown holes) ✓");
return 0;
}
println!(
"candor unverified — {} function(s) PASS their policy but aren't PROVABLY clean:\n",
holes.len()
);
let mut upgrades: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for h in &holes {
let (rule, upgrade) = rule_and_upgrade(h.rule);
upgrades.insert(upgrade.clone());
println!(" `{}` (in `{rule}`)", h.func.func);
let why = if h.func.unknown_why.is_empty() {
"an unresolvable call".to_string()
} else {
h.func.unknown_why.join(", ")
};
println!(" is Unknown ({why}) — candor can't confirm it's free of the forbidden effect(s);");
println!(" the Unknown could hide the very effect the rule forbids (e.g. a fn/closure-injected port).");
println!(" → make it provable: add `{upgrade}`");
println!();
}
println!(" The gate still PASSES — this is advisory. To REQUIRE provable purity, add:");
for u in &upgrades {
println!(" {u}");
}
if strict {
return 1;
}
0
}