use axon::ir_nodes::IRProgram;
use axon::pcc::{
check_proof, generate_all_proofs, generate_authorization_coverage_proofs, CheckOutcome,
PropertyClass, Witness,
};
const VERSION: &str = "2.44.0-test";
const FLOW: &str = "flow Chat() -> Unit { step S { ask: \"hi\" } }\n";
const SHIELD: &str = "shield Guard { scan: [pii_leak] on_breach: halt }\n";
fn ir_of(src: &str) -> IRProgram {
let tokens = axon::lexer::Lexer::new(src, "<t>").tokenize().expect("lex");
let program = axon::parser::Parser::new(tokens).parse().expect("parse");
axon::ir_generator::IRGenerator::new().generate(&program)
}
#[test]
fn public_endpoint_generates_a_verified_proof() {
let ir = ir_of(&format!(
"{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat public: true }}"
));
let proofs = generate_authorization_coverage_proofs(&ir, VERSION);
assert_eq!(proofs.len(), 1, "one dispatching endpoint => one proof");
assert_eq!(proofs[0].property, PropertyClass::AuthorizationCoverage);
assert_eq!(check_proof(&proofs[0], &ir), CheckOutcome::Verified);
}
#[test]
fn each_coverage_discipline_verifies() {
for (label, src) in [
(
"requires",
format!("{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat requires: [flow.execute] }}"),
),
(
"shield",
format!("{SHIELD}{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat shield: Guard }}"),
),
(
"compliance",
format!("{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat compliance: [SOC2] }}"),
),
] {
let ir = ir_of(&src);
let proofs = generate_authorization_coverage_proofs(&ir, VERSION);
assert_eq!(proofs.len(), 1, "{label}: one proof");
assert_eq!(
check_proof(&proofs[0], &ir),
CheckOutcome::Verified,
"{label}: coverage must verify"
);
}
}
#[test]
fn uncovered_endpoint_is_refuted() {
let ir = ir_of(&format!(
"{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat }}"
));
let proofs = generate_authorization_coverage_proofs(&ir, VERSION);
assert_eq!(proofs.len(), 1);
match check_proof(&proofs[0], &ir) {
CheckOutcome::Refuted { reason } => assert!(
reason.contains("axon-T890"),
"refutation must cite T890, got: {reason}"
),
other => panic!("uncovered boundary must be REFUTED, got {other:?}"),
}
}
#[test]
fn non_dispatching_endpoint_yields_no_proof() {
let ir = ir_of("axonendpoint E { method: POST path: \"/c\" }");
assert!(
generate_authorization_coverage_proofs(&ir, VERSION).is_empty(),
"an endpoint that dispatches nothing crosses no boundary => no proof"
);
}
#[test]
fn forged_authorized_witness_is_refuted() {
let ir = ir_of(&format!(
"{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat }}"
));
let mut proof = generate_authorization_coverage_proofs(&ir, VERSION)
.into_iter()
.next()
.expect("one proof");
if let Witness::AuthorizationCoverage(w) = &mut proof.witness {
w.authorized = true; w.public = true;
} else {
panic!("expected an AuthorizationCoverage witness");
}
match check_proof(&proof, &ir) {
CheckOutcome::Refuted { reason } => assert!(
reason.contains("re-derivation"),
"a forged witness must be caught by recomputation, got: {reason}"
),
other => panic!("forged witness must be REFUTED, got {other:?}"),
}
}
#[test]
fn generate_all_includes_authorization_coverage() {
let ir = ir_of(&format!(
"{FLOW}axonendpoint E {{ method: POST path: \"/c\" execute: Chat public: true }}"
));
let all = generate_all_proofs(&ir, VERSION);
assert!(
all.iter()
.any(|p| p.property == PropertyClass::AuthorizationCoverage),
"generate_all_proofs must include the AuthorizationCoverage class"
);
}