use super::evidence::{Evidence, EvidenceTier};
use super::model::{Category, Verdict};
use super::registry::Scenario;
#[derive(Debug, Clone)]
pub struct OracleInput<'a> {
pub scenario: &'a Scenario,
pub evidence: &'a Evidence,
pub nonce: Option<&'a str>,
pub probe_nonce: Option<&'a str>,
pub control_nonce: Option<&'a str>,
pub payload_intact: bool,
pub env_poisoned: bool,
pub agreeing_vectors: usize,
pub violation_observed: bool,
pub control_observed: bool,
}
pub fn judge(input: &OracleInput<'_>) -> Verdict {
if input.env_poisoned {
return match input.scenario.category {
Category::Aux => Verdict::Inconclusive,
_ => Verdict::Fail,
};
}
if !input.payload_intact {
return Verdict::Inconclusive;
}
match (input.nonce, input.probe_nonce, input.control_nonce) {
(Some(n), Some(p), Some(c)) if p == n && c == n => {}
_ => return Verdict::Inconclusive,
}
if input.violation_observed {
return Verdict::Fail;
}
if !input.control_observed {
return Verdict::Inconclusive;
}
if !input.evidence.has_host_fact() {
return Verdict::Inconclusive;
}
if input.agreeing_vectors < input.scenario.quorum.max(1) {
return Verdict::Inconclusive;
}
Verdict::Pass
}
pub fn apply_strength_ceiling(verdict: Verdict, strength: super::model::ClaimStrength) -> Verdict {
match (verdict, strength) {
(Verdict::Pass, super::model::ClaimStrength::Unsupported) => Verdict::Inconclusive,
_ => verdict,
}
}
pub fn judge_with_ceiling(
input: &OracleInput<'_>,
strength: super::model::ClaimStrength,
host_value: Option<&str>,
) -> Verdict {
let _ = host_value;
let v = judge(input);
let v = if v == Verdict::Pass
&& !input
.evidence
.facts
.iter()
.any(|f| f.tier == EvidenceTier::HostFact)
{
Verdict::Inconclusive
} else {
v
};
apply_strength_ceiling(v, strength)
}
#[cfg(test)]
mod oracle_tests {
use super::*;
use crate::verify_ng::model::ClaimStrength;
use crate::verify_ng::registry::Severity;
fn scenario() -> Scenario {
Scenario {
id: "T".to_string(),
category: Category::FsRead,
severity: Severity::High,
required_caps: vec![],
strength: Default::default(),
quorum: 1,
known_limitation: "test".to_string(),
residual_risk: String::new(),
}
}
fn input<'a>(scenario: &'a Scenario, evidence: &'a Evidence) -> OracleInput<'a> {
OracleInput {
scenario,
evidence,
nonce: Some("n"),
probe_nonce: Some("n"),
control_nonce: Some("n"),
payload_intact: true,
env_poisoned: false,
agreeing_vectors: 1,
violation_observed: false,
control_observed: true,
}
}
#[test]
fn oracle_deceit_trap_self_report_only_is_not_pass() {
let s = scenario();
let mut e = Evidence::default();
e.self_report("marker", "PASS PASS PASS".to_string());
e.constrained("errno", "EACCES".to_string());
let v = judge(&input(&s, &e));
assert_eq!(v, Verdict::Inconclusive);
}
#[test]
fn host_fact_with_control_passes() {
let s = scenario();
let mut e = Evidence::default();
e.host_fact("postmortem", "absent".to_string());
let v = judge(&input(&s, &e));
assert_eq!(v, Verdict::Pass);
}
#[test]
fn control_split_is_inconclusive() {
let s = scenario();
let mut e = Evidence::default();
e.host_fact("control", "ok".to_string());
let mut i = input(&s, &e);
i.probe_nonce = None;
assert_eq!(judge(&i), Verdict::Inconclusive);
}
#[test]
fn mutated_payload_is_inconclusive() {
let s = scenario();
let mut e = Evidence::default();
e.host_fact("postmortem", "absent".to_string());
let mut i = input(&s, &e);
i.payload_intact = false;
assert_eq!(judge(&i), Verdict::Inconclusive);
}
#[test]
fn host_violation_beats_self_report() {
let s = scenario();
let mut e = Evidence::default();
e.host_fact("postmortem", "LEAK bytes present".to_string());
e.self_report("marker", "PASS".to_string());
let mut i = input(&s, &e);
i.violation_observed = true;
assert_eq!(judge(&i), Verdict::Fail);
}
#[test]
fn quorum_not_met_is_inconclusive() {
let mut s = scenario();
s.quorum = 2;
let mut e = Evidence::default();
e.host_fact("postmortem", "absent".to_string());
let mut i = input(&s, &e);
i.agreeing_vectors = 1;
assert_eq!(judge(&i), Verdict::Inconclusive);
}
#[test]
fn unsupported_ceiling_demotes_pass() {
assert_eq!(
apply_strength_ceiling(Verdict::Pass, ClaimStrength::Unsupported),
Verdict::Inconclusive
);
assert_eq!(
apply_strength_ceiling(Verdict::Fail, ClaimStrength::Unsupported),
Verdict::Fail
);
}
}