#![allow(dead_code)]
use std::fs;
use std::path::Path;
use crate::audit_cli::{canonical_json_utf8, write_or_print};
use crate::ir_generator::IRGenerator;
use crate::ir_nodes::IRProgram;
use crate::lexer::Lexer;
use crate::parser::Parser;
use crate::pcc::{check_bundle, generate_all_proofs, CheckOutcome, ProofBundle};
use crate::runner::AXON_VERSION;
fn compile_file(file: &str) -> Result<IRProgram, i32> {
let path = Path::new(file);
if !path.exists() {
eprintln!("X File not found: {file}");
return Err(2);
}
let source = match fs::read_to_string(path) {
Ok(s) => s,
Err(e) => {
eprintln!("X Cannot read {file}: {e}");
return Err(2);
}
};
let tokens = match Lexer::new(&source, file).tokenize() {
Ok(t) => t,
Err(e) => {
eprintln!("X Lex error in {file}: {}", e.message);
return Err(1);
}
};
let program = match Parser::new(tokens).parse() {
Ok(p) => p,
Err(e) => {
eprintln!("X Parse error in {file}: {}", e.message);
return Err(1);
}
};
use crate::type_checker::TypeChecker;
let diagnostics = TypeChecker::new(&program).check();
if !diagnostics.is_empty() {
eprintln!(
"X {file} has {} type error(s) — run 'axon check' for details.",
diagnostics.len()
);
return Err(1);
}
Ok(IRGenerator::new().generate(&program))
}
pub fn run_pcc_prove(file: &str, output: Option<&str>) -> i32 {
let ir = match compile_file(file) {
Ok(ir) => ir,
Err(code) => return code,
};
let proofs = generate_all_proofs(&ir, AXON_VERSION);
let bundle = ProofBundle {
axon_version: AXON_VERSION.to_string(),
artifact_digest: crate::pcc::artifact_digest(&ir),
proofs,
};
let value = match serde_json::to_value(&bundle) {
Ok(v) => v,
Err(e) => {
eprintln!("X failed to serialize proof bundle: {e}");
return 2;
}
};
let text = canonical_json_utf8(&value);
write_or_print(&text, output, "PCC proof bundle written to")
}
pub fn run_pcc_verify(file: &str, bundle_path: &str) -> i32 {
let ir = match compile_file(file) {
Ok(ir) => ir,
Err(code) => return code,
};
let bundle_text = match fs::read_to_string(bundle_path) {
Ok(s) => s,
Err(e) => {
eprintln!("X Cannot read proof bundle {bundle_path}: {e}");
return 2;
}
};
let bundle: ProofBundle = match serde_json::from_str(&bundle_text) {
Ok(b) => b,
Err(e) => {
eprintln!("X Malformed proof bundle {bundle_path}: {e}");
return 2;
}
};
if bundle.proofs.is_empty() {
println!("PCC verify: bundle declares no proofs for {file} — nothing to verify (OK).");
return 0;
}
let report = check_bundle(&bundle, &ir);
let total = report.results.len();
let mut verified = 0usize;
for r in &report.results {
let slug = r.property.slug();
let subject = &r.subject;
match &r.outcome {
CheckOutcome::Verified => {
verified += 1;
println!(" OK [{slug}] {subject} — verified");
}
CheckOutcome::Refuted { reason } => {
println!(" FAIL [{slug}] {subject} — refuted: {reason}");
}
CheckOutcome::DigestMismatch => {
println!(
" FAIL [{slug}] {subject} — digest mismatch: this proof is not for {file}"
);
}
CheckOutcome::UnknownProperty => {
println!(
" FAIL [{slug}] {subject} — unknown property (checker does not understand this proof shape)"
);
}
}
}
if report.all_verified() {
println!("PCC verify: {verified}/{total} proofs VERIFIED against {file}.");
0
} else {
let failed = total - verified;
println!("PCC verify: {failed}/{total} proofs FAILED against {file} ({verified} verified).");
1
}
}
#[cfg(test)]
mod tests {
use super::*;
fn temp_path(stem: &str, ext: &str) -> std::path::PathBuf {
std::env::temp_dir().join(format!("axon_pcc_cli_{stem}.{ext}"))
}
#[test]
fn prove_then_verify_empty_source_roundtrips() {
let src = temp_path("empty", "axon");
let bundle = temp_path("empty", "json");
fs::write(&src, "").expect("write source");
let prove_code =
run_pcc_prove(src.to_str().unwrap(), Some(bundle.to_str().unwrap()));
assert_eq!(prove_code, 0, "prove should succeed on empty source");
let verify_code =
run_pcc_verify(src.to_str().unwrap(), bundle.to_str().unwrap());
assert_eq!(verify_code, 0, "verify should pass an empty bundle");
let _ = fs::remove_file(&src);
let _ = fs::remove_file(&bundle);
}
#[test]
fn verify_tampered_bundle_exits_one() {
let src = temp_path("tamper", "axon");
let bundle = temp_path("tamper", "json");
fs::write(&src, "").expect("write source");
let bogus = r#"{
"axon_version": "test",
"artifact_digest": "deadbeef",
"proofs": [
{
"property": "ComplianceCoverage",
"artifact_digest": "deadbeef",
"witness": {
"kind": "ComplianceCoverage",
"endpoint_name": "Ghost",
"required_classes": ["HIPAA"],
"shield_ref": "",
"shield_present": false,
"provided_classes": [],
"unknown_classes": [],
"uncovered_classes": ["HIPAA"]
},
"axon_version": "test"
}
]
}"#;
fs::write(&bundle, bogus).expect("write bundle");
let verify_code =
run_pcc_verify(src.to_str().unwrap(), bundle.to_str().unwrap());
assert_eq!(verify_code, 1, "verify must reject a tampered bundle");
let _ = fs::remove_file(&src);
let _ = fs::remove_file(&bundle);
}
#[test]
fn verify_malformed_bundle_exits_two() {
let src = temp_path("malformed", "axon");
let bundle = temp_path("malformed", "json");
fs::write(&src, "").expect("write source");
fs::write(&bundle, "{ not json").expect("write bundle");
let verify_code =
run_pcc_verify(src.to_str().unwrap(), bundle.to_str().unwrap());
assert_eq!(verify_code, 2, "malformed bundle is an operational error");
let _ = fs::remove_file(&src);
let _ = fs::remove_file(&bundle);
}
#[test]
fn prove_missing_file_exits_two() {
assert_eq!(
run_pcc_prove("Z:/definitely/missing.axon", None),
2,
"missing source is an operational error"
);
}
}