use super::compile::{compile_admission, compile_budget_detail, AdmissionShape};
use super::eval::{evaluate, Lane};
use super::program::Width;
use super::schedule::{schedule_refusal, ScheduleInputs};
use super::schedule_circuit::{
compile_schedule_membrane, encode_schedule, shape_of as schedule_shape_of,
};
const FIELD_BITS: u32 = 8;
const ENFORCEMENT_BITS: u32 = 2;
const BUDGET_MEMBRANE: u64 = 4;
const SCHEDULE_MEMBRANE: u64 = 6;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RequirementInputs {
pub enforcement: u8,
pub evidence_required: u64,
pub evidence_available: u64,
pub conflict_present: u64,
pub conflict_forbidden: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct BudgetInputs {
pub limit: u64,
pub available: u64,
pub derived_min: u64,
pub guarantee_required: u8,
pub guarantee_available: u8,
pub evidence_required: u64,
pub evidence_available: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AdmissionInputs {
pub planned_profile: u64,
pub live_profile: u64,
pub requirements: Vec<RequirementInputs>,
pub budget: Vec<BudgetInputs>,
pub schedule: ScheduleInputs,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum AdmissionOutcome {
Admitted {
trace: Vec<bool>,
},
Refused {
membrane: u64,
refusal_code: u64,
trace: Vec<bool>,
budget_dimension: u64,
budget_reason: u64,
schedule_reason: u64,
},
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum AdmissionDivergence {
OutcomeMismatch {
reference: Box<AdmissionOutcome>,
circuit: Box<AdmissionOutcome>,
},
CircuitError {
reference: Box<AdmissionOutcome>,
reason: &'static str,
},
}
impl std::fmt::Display for AdmissionDivergence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::OutcomeMismatch { reference, circuit } => write!(
f,
"admission divergence: reference {reference:?} != circuit {circuit:?}"
),
Self::CircuitError { reference, reason } => write!(
f,
"admission divergence: circuit failed ({reason}) where reference was {reference:?}"
),
}
}
}
impl std::error::Error for AdmissionDivergence {}
pub(crate) fn mask(value: u64, bits: u32) -> u64 {
if bits >= 64 {
value
} else {
value & ((1u64 << bits) - 1)
}
}
fn reference_trace(inputs: &AdmissionInputs) -> Vec<bool> {
let drift = mask(inputs.planned_profile, FIELD_BITS) == mask(inputs.live_profile, FIELD_BITS);
let support = inputs
.requirements
.iter()
.all(|r| mask(u64::from(r.enforcement), ENFORCEMENT_BITS) >= 1);
let evidence = inputs.requirements.iter().all(|r| {
(mask(r.evidence_required, FIELD_BITS) & !mask(r.evidence_available, FIELD_BITS)) == 0
});
let budget = inputs.budget.iter().all(|d| budget_dim_reason(d) == 0);
let conflict = inputs.requirements.iter().all(|r| {
(mask(r.conflict_present, FIELD_BITS) & mask(r.conflict_forbidden, FIELD_BITS)) == 0
});
let schedule = reference_schedule_detail(inputs) == 0;
vec![drift, support, evidence, budget, conflict, schedule]
}
fn reference_schedule_detail(inputs: &AdmissionInputs) -> u64 {
schedule_refusal(&inputs.schedule).map_or(0, |reason| u64::from(reason.code()))
}
fn budget_dim_reason(d: &BudgetInputs) -> u8 {
let limit = mask(d.limit, FIELD_BITS);
let available = mask(d.available, FIELD_BITS);
let derived = mask(d.derived_min, FIELD_BITS);
let g_req = mask(u64::from(d.guarantee_required), ENFORCEMENT_BITS);
let g_avail = mask(u64::from(d.guarantee_available), ENFORCEMENT_BITS);
let e_req = mask(d.evidence_required, FIELD_BITS);
let e_avail = mask(d.evidence_available, FIELD_BITS);
if derived > limit {
1
} else if limit > available {
2
} else if g_req > g_avail {
3
} else if (e_req & !e_avail) != 0 {
4
} else {
0
}
}
fn reference_budget_detail(inputs: &AdmissionInputs) -> u64 {
for (index, d) in inputs.budget.iter().enumerate() {
let reason = budget_dim_reason(d);
if reason != 0 {
let dimension = u64::try_from(index + 1).unwrap_or(0);
return (dimension << 3) | u64::from(reason);
}
}
0
}
fn budget_selectors(membrane: u64, detail: u64) -> (u64, u64) {
if membrane == BUDGET_MEMBRANE {
(detail >> 3, detail & 0b111)
} else {
(0, 0)
}
}
fn schedule_selector(membrane: u64, detail: u64) -> u64 {
if membrane == SCHEDULE_MEMBRANE {
detail
} else {
0
}
}
pub(crate) struct MembraneDetails {
pub budget: u64,
pub schedule: u64,
}
pub(crate) fn outcome_from_trace(trace: Vec<bool>, details: &MembraneDetails) -> AdmissionOutcome {
match trace.iter().position(|pass| !pass) {
None => AdmissionOutcome::Admitted { trace },
Some(i) => {
let index = u64::try_from(i + 1).unwrap_or(0);
let (budget_dimension, budget_reason) = budget_selectors(index, details.budget);
let schedule_reason = schedule_selector(index, details.schedule);
AdmissionOutcome::Refused {
membrane: index,
refusal_code: index,
trace,
budget_dimension,
budget_reason,
schedule_reason,
}
}
}
}
#[must_use]
pub fn reference_admission(inputs: &AdmissionInputs) -> AdmissionOutcome {
let details = MembraneDetails {
budget: reference_budget_detail(inputs),
schedule: reference_schedule_detail(inputs),
};
outcome_from_trace(reference_trace(inputs), &details)
}
fn field_width() -> Width {
Width::new(8).expect("8 is within 1..=MAX_WIDTH")
}
fn enforcement_lane_width() -> Width {
Width::new(2).expect("2 is within 1..=MAX_WIDTH")
}
fn shape_of(inputs: &AdmissionInputs) -> AdmissionShape {
AdmissionShape {
requirements: inputs.requirements.len(),
budget_dims: inputs.budget.len(),
budget_width: field_width(),
evidence_width: field_width(),
conflict_width: field_width(),
hash_width: field_width(),
schedule: schedule_shape_of(&inputs.schedule),
}
}
fn encode_lane(value: u64, width: Width) -> Lane {
Lane::from_le_bytes(&value.to_le_bytes(), width)
}
fn encode(inputs: &AdmissionInputs) -> Vec<Lane> {
let field = field_width();
let enf = enforcement_lane_width();
let mut lanes = vec![
encode_lane(inputs.planned_profile, field),
encode_lane(inputs.live_profile, field),
];
lanes.extend(
inputs
.requirements
.iter()
.map(|r| encode_lane(u64::from(r.enforcement), enf)),
);
lanes.extend(
inputs
.requirements
.iter()
.map(|r| encode_lane(r.evidence_required, field)),
);
lanes.extend(
inputs
.requirements
.iter()
.map(|r| encode_lane(r.evidence_available, field)),
);
lanes.extend(encode_budget(inputs));
lanes.extend(
inputs
.requirements
.iter()
.map(|r| encode_lane(r.conflict_present, field)),
);
lanes.extend(
inputs
.requirements
.iter()
.map(|r| encode_lane(r.conflict_forbidden, field)),
);
lanes.extend(encode_schedule(
&inputs.schedule,
&schedule_shape_of(&inputs.schedule),
));
lanes
}
fn encode_budget(inputs: &AdmissionInputs) -> Vec<Lane> {
let field = field_width();
let enf = enforcement_lane_width();
let mut lanes = Vec::new();
lanes.extend(inputs.budget.iter().map(|d| encode_lane(d.limit, field)));
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(d.available, field)),
);
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(d.derived_min, field)),
);
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(u64::from(d.guarantee_required), enf)),
);
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(u64::from(d.guarantee_available), enf)),
);
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(d.evidence_required, field)),
);
lanes.extend(
inputs
.budget
.iter()
.map(|d| encode_lane(d.evidence_available, field)),
);
lanes
}
fn circuit_admission(inputs: &AdmissionInputs) -> Result<AdmissionOutcome, &'static str> {
let shape = shape_of(inputs);
let program = compile_admission(&shape).map_err(|_| "circuit compilation failed")?;
let decision = evaluate(&program, &encode(inputs)).map_err(|_| "circuit evaluation failed")?;
if decision.admit {
return Ok(AdmissionOutcome::Admitted {
trace: decision.membranes,
});
}
let budget_detail = circuit_budget_detail(inputs)?;
let (budget_dimension, budget_reason) = budget_selectors(decision.refusal_code, budget_detail);
let schedule_reason =
schedule_selector(decision.refusal_code, circuit_schedule_detail(inputs)?);
Ok(AdmissionOutcome::Refused {
membrane: decision.refusal_code,
refusal_code: decision.refusal_code,
trace: decision.membranes,
budget_dimension,
budget_reason,
schedule_reason,
})
}
fn circuit_budget_detail(inputs: &AdmissionInputs) -> Result<u64, &'static str> {
let program = compile_budget_detail(inputs.budget.len(), field_width(), field_width())
.map_err(|_| "budget detail compilation failed")?;
Ok(evaluate(&program, &encode_budget(inputs))
.map_err(|_| "budget detail evaluation failed")?
.refusal_code)
}
fn circuit_schedule_detail(inputs: &AdmissionInputs) -> Result<u64, &'static str> {
let schedule_shape = schedule_shape_of(&inputs.schedule);
let program = compile_schedule_membrane(&schedule_shape)
.map_err(|_| "schedule detail compilation failed")?;
Ok(evaluate(
&program,
&encode_schedule(&inputs.schedule, &schedule_shape),
)
.map_err(|_| "schedule detail evaluation failed")?
.refusal_code)
}
pub(crate) fn decide(
reference: AdmissionOutcome,
circuit: Result<AdmissionOutcome, &'static str>,
) -> Result<AdmissionOutcome, AdmissionDivergence> {
match circuit {
Ok(circuit) if circuit == reference => Ok(reference),
Ok(circuit) => Err(AdmissionDivergence::OutcomeMismatch {
reference: Box::new(reference),
circuit: Box::new(circuit),
}),
Err(reason) => Err(AdmissionDivergence::CircuitError {
reference: Box::new(reference),
reason,
}),
}
}
pub fn shadow_check(inputs: &AdmissionInputs) -> Result<AdmissionOutcome, AdmissionDivergence> {
decide(reference_admission(inputs), circuit_admission(inputs))
}
#[cfg(test)]
mod shadow_tests {
use super::super::schedule::{PrimitiveDeclInputs, ScheduleInputs, ScheduleSlotInputs};
use super::{
decide, reference_admission, shadow_check, AdmissionDivergence, AdmissionInputs,
AdmissionOutcome, BudgetInputs, RequirementInputs,
};
fn passing_schedule() -> ScheduleInputs {
ScheduleInputs {
declarations: vec![PrimitiveDeclInputs {
phase: 0,
covers: 0b1,
prerequisites: 0,
conflicts: 0,
decl_digest: 0xAA,
param_digest: 0xBB,
}],
schedule: vec![ScheduleSlotInputs {
primitive: 0,
claimed_decl_digest: 0xAA,
claimed_param_digest: 0xBB,
}],
required: 0b1,
}
}
fn all_pass() -> AdmissionInputs {
AdmissionInputs {
planned_profile: 7,
live_profile: 7, requirements: vec![RequirementInputs {
enforcement: 2, evidence_required: 0b0101,
evidence_available: 0b1111, conflict_present: 0b0001,
conflict_forbidden: 0b0010, }],
budget: vec![BudgetInputs {
limit: 10,
available: 20,
derived_min: 0,
guarantee_required: 1, guarantee_available: 2, evidence_required: 0b0001,
evidence_available: 0b1111, }],
schedule: passing_schedule(),
}
}
#[test]
fn shadow_admits_and_both_paths_agree() {
let outcome = shadow_check(&all_pass()).expect("no divergence");
assert_eq!(
outcome,
AdmissionOutcome::Admitted {
trace: vec![true, true, true, true, true, true],
}
);
}
#[test]
fn shadow_refuses_at_first_failing_membrane_and_both_paths_agree() {
let mut drift = all_pass();
drift.live_profile = 1;
assert_refused_at(&drift, 1);
let mut support = all_pass();
support.requirements[0].enforcement = 0;
assert_refused_at(&support, 2);
let mut evidence = all_pass();
evidence.requirements[0].evidence_required = 0b1000;
evidence.requirements[0].evidence_available = 0b0001;
assert_refused_at(&evidence, 3);
let mut budget = all_pass();
budget.budget[0].limit = 99;
budget.budget[0].available = 1; assert_refused_at(&budget, 4);
let mut conflict = all_pass();
conflict.requirements[0].conflict_present = 0b0011;
conflict.requirements[0].conflict_forbidden = 0b0011;
assert_refused_at(&conflict, 5);
let mut schedule = all_pass();
schedule.schedule.schedule[0].primitive = 9; assert_refused_at(&schedule, 6);
}
#[test]
fn schedule_membrane_refusal_carries_the_schedule_reason_with_circuit_parity() {
let mut inputs = all_pass();
inputs.schedule = ScheduleInputs {
declarations: vec![
PrimitiveDeclInputs {
phase: 0,
covers: 0b01,
prerequisites: 0,
conflicts: 0,
decl_digest: 0xA0,
param_digest: 0xB0,
},
PrimitiveDeclInputs {
phase: 0,
covers: 0b10,
prerequisites: 0,
conflicts: 0,
decl_digest: 0xA1,
param_digest: 0xB1,
},
],
schedule: vec![
ScheduleSlotInputs {
primitive: 1,
claimed_decl_digest: 0xA1,
claimed_param_digest: 0xB1,
},
ScheduleSlotInputs {
primitive: 0,
claimed_decl_digest: 0xA0,
claimed_param_digest: 0xB0,
},
],
required: 0b11,
};
let got = match shadow_check(&inputs).expect("no divergence") {
AdmissionOutcome::Refused {
membrane,
schedule_reason,
..
} => Some((membrane, schedule_reason)),
AdmissionOutcome::Admitted { .. } => None,
};
assert_eq!(
got,
Some((6, 9)),
"schedule refusal names reason 9 (NonCanonical)"
);
}
fn assert_refused_at(inputs: &AdmissionInputs, expected: u64) {
let refusal = match shadow_check(inputs).expect("no divergence") {
AdmissionOutcome::Refused {
membrane,
refusal_code,
..
} => Some((membrane, refusal_code)),
AdmissionOutcome::Admitted { .. } => None,
};
assert_eq!(
refusal,
Some((expected, expected)),
"must refuse at the first failing membrane ({expected})"
);
}
#[test]
fn divergence_detector_fires_on_a_planted_outcome_mismatch() {
let reference = reference_admission(&all_pass());
let wrong_circuit = AdmissionOutcome::Refused {
membrane: 2,
refusal_code: 2,
trace: vec![true, false, true, true, true, true],
budget_dimension: 0,
budget_reason: 0,
schedule_reason: 0,
};
let result = decide(reference.clone(), Ok(wrong_circuit.clone()));
assert_eq!(
result,
Err(AdmissionDivergence::OutcomeMismatch {
reference: Box::new(reference),
circuit: Box::new(wrong_circuit),
})
);
}
fn assert_budget_refusal(inputs: &AdmissionInputs, dimension: u64, reason: u64) {
let got = match shadow_check(inputs).expect("no divergence (reference == circuit)") {
AdmissionOutcome::Refused {
membrane,
budget_dimension,
budget_reason,
..
} => Some((membrane, budget_dimension, budget_reason)),
AdmissionOutcome::Admitted { .. } => None,
};
assert_eq!(
got,
Some((4, dimension, reason)),
"budget refusal must name dimension {dimension}, reason {reason}"
);
}
#[test]
fn budget_refusal_names_the_dimension_and_reason_with_circuit_parity() {
let two_dim = || {
let mut inputs = all_pass();
let dim0 = inputs.budget[0];
inputs.budget.push(dim0); inputs
};
let mut below = two_dim();
below.budget[0].limit = 2;
below.budget[0].derived_min = 5; assert_budget_refusal(&below, 1, 1);
let mut capacity = two_dim();
capacity.budget[0].limit = 99;
capacity.budget[0].available = 1; assert_budget_refusal(&capacity, 1, 2);
let mut guarantee = two_dim();
guarantee.budget[0].guarantee_required = 2; guarantee.budget[0].guarantee_available = 1; assert_budget_refusal(&guarantee, 1, 3);
let mut evidence = two_dim();
evidence.budget[0].evidence_required = 0b1000;
evidence.budget[0].evidence_available = 0b0001; assert_budget_refusal(&evidence, 1, 4);
let mut second = two_dim();
second.budget[1].limit = 99;
second.budget[1].available = 1;
assert_budget_refusal(&second, 2, 2);
}
#[test]
fn agreement_is_not_a_divergence() {
let reference = reference_admission(&all_pass());
assert!(decide(reference.clone(), Ok(reference)).is_ok());
}
#[test]
fn a_circuit_error_is_a_divergence() {
let reference = reference_admission(&all_pass());
let result = decide(reference, Err("circuit evaluation failed"));
assert!(matches!(
result,
Err(AdmissionDivergence::CircuitError {
reason: "circuit evaluation failed",
..
})
));
}
}