use super::compile::{compose_membranes, support_check, CircuitBuilder};
use super::eval::{evaluate, Lane};
use super::program::{AdmissionProgram, Outputs, ProgramError, Width};
use super::shadow::{
decide, mask, outcome_from_trace, AdmissionDivergence, AdmissionOutcome, MembraneDetails,
};
const ENFORCEMENT_BITS: u32 = 2;
const EVIDENCE_BITS: u32 = 16;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlannerInputs {
pub enforcement: Vec<u8>,
pub evidence_required: u16,
pub evidence_available: u16,
}
#[must_use]
pub fn planner_reference(inputs: &PlannerInputs) -> AdmissionOutcome {
let support = inputs
.enforcement
.iter()
.all(|e| mask(u64::from(*e), ENFORCEMENT_BITS) >= 1);
let evidence = (mask(u64::from(inputs.evidence_required), EVIDENCE_BITS)
& !mask(u64::from(inputs.evidence_available), EVIDENCE_BITS))
== 0;
outcome_from_trace(
vec![support, evidence],
&MembraneDetails {
budget: 0,
schedule: 0,
},
)
}
fn enforcement_width() -> Width {
Width::new(2).expect("2 is within 1..=MAX_WIDTH")
}
fn evidence_width() -> Width {
Width::new(16).expect("16 is within 1..=MAX_WIDTH")
}
fn compile_planner_circuit(reqs: usize) -> Result<AdmissionProgram, ProgramError> {
let mut builder = CircuitBuilder::new();
let enf_width = enforcement_width();
let evid_width = evidence_width();
let enforcements: Vec<_> = (0..reqs).map(|_| builder.input(enf_width)).collect();
let required = builder.input(evid_width);
let available = builder.input(evid_width);
let support = support_check(&mut builder, &enforcements, enf_width);
let evidence = builder.bitset_subset(required, available);
let (admit, refusal_code) = compose_membranes(&mut builder, &[support, evidence]);
builder.finish(Outputs {
admit,
refusal_code,
membranes: vec![support, evidence],
})
}
fn encode(inputs: &PlannerInputs) -> Vec<Lane> {
let enf_width = enforcement_width();
let evid_width = evidence_width();
let mut lanes: Vec<Lane> = inputs
.enforcement
.iter()
.map(|e| Lane::from_le_bytes(&u64::from(*e).to_le_bytes(), enf_width))
.collect();
lanes.push(Lane::from_le_bytes(
&u64::from(inputs.evidence_required).to_le_bytes(),
evid_width,
));
lanes.push(Lane::from_le_bytes(
&u64::from(inputs.evidence_available).to_le_bytes(),
evid_width,
));
lanes
}
fn planner_circuit(inputs: &PlannerInputs) -> Result<AdmissionOutcome, &'static str> {
let program = compile_planner_circuit(inputs.enforcement.len())
.map_err(|_| "circuit compilation failed")?;
let decision = evaluate(&program, &encode(inputs)).map_err(|_| "circuit evaluation failed")?;
Ok(if decision.admit {
AdmissionOutcome::Admitted {
trace: decision.membranes,
}
} else {
AdmissionOutcome::Refused {
membrane: decision.refusal_code,
refusal_code: decision.refusal_code,
trace: decision.membranes,
budget_dimension: 0,
budget_reason: 0,
schedule_reason: 0,
}
})
}
pub fn planner_shadow_check(
inputs: &PlannerInputs,
) -> Result<AdmissionOutcome, AdmissionDivergence> {
decide(planner_reference(inputs), planner_circuit(inputs))
}
#[cfg(test)]
mod planner_shadow_tests {
use super::super::shadow::{decide, AdmissionDivergence, AdmissionOutcome};
use super::{planner_reference, planner_shadow_check, PlannerInputs};
fn admitted() -> AdmissionOutcome {
AdmissionOutcome::Admitted {
trace: vec![true, true],
}
}
fn refused_at(membrane: u64, trace: Vec<bool>) -> AdmissionOutcome {
AdmissionOutcome::Refused {
membrane,
refusal_code: membrane,
trace,
budget_dimension: 0,
budget_reason: 0,
schedule_reason: 0,
}
}
#[test]
fn planner_circuit_matches_the_reference_exhaustively() {
for e0 in 0..4u8 {
for e1 in 0..4u8 {
for required in 0..16u16 {
for available in 0..16u16 {
let inputs = PlannerInputs {
enforcement: vec![e0, e1],
evidence_required: required,
evidence_available: available,
};
let outcome = planner_shadow_check(&inputs).expect("no divergence");
let support = e0 >= 1 && e1 >= 1;
let evidence = (required & !available) == 0;
let expected = match (support, evidence) {
(true, true) => admitted(),
(false, _) => refused_at(1, vec![false, evidence]),
(true, false) => refused_at(2, vec![true, false]),
};
assert_eq!(
outcome, expected,
"enf=[{e0},{e1}] req={required:04b} avail={available:04b}"
);
}
}
}
}
}
#[test]
fn reference_orders_support_before_evidence() {
let inputs = PlannerInputs {
enforcement: vec![0],
evidence_required: 0b1,
evidence_available: 0b0,
};
assert_eq!(
planner_reference(&inputs),
refused_at(1, vec![false, false])
);
}
#[test]
fn divergence_detector_fires_on_a_planted_planner_mismatch() {
let reference = planner_reference(&PlannerInputs {
enforcement: vec![2],
evidence_required: 0,
evidence_available: 0,
});
let wrong = AdmissionOutcome::Refused {
membrane: 2,
refusal_code: 2,
trace: vec![true, false],
budget_dimension: 0,
budget_reason: 0,
schedule_reason: 0,
};
assert_eq!(
decide(reference.clone(), Ok(wrong.clone())),
Err(AdmissionDivergence::OutcomeMismatch {
reference: Box::new(reference),
circuit: Box::new(wrong),
})
);
}
}