use crate::protocol::layers::{TrustEnvelope, TrustFactor};
use m1nd_core::calibration::{self, CalibrationRow};
pub const VERDICT_UNPROVABLE: &str = "unprovable";
pub const FACTOR_TRUST_BAND: &str = "trust_band";
pub const FACTOR_BINDING: &str = "binding";
pub const WEIGHT_TRUST_BAND: f32 = 1.0;
pub const WEIGHT_BINDING: f32 = 1.0;
#[derive(Clone, Debug)]
pub struct FactorInput {
pub name: String,
pub band: String,
pub weight: f32,
pub known: bool,
pub reliability: f32,
pub repair_hint: Option<&'static str>,
}
impl FactorInput {
fn to_factor(&self) -> TrustFactor {
TrustFactor {
name: self.name.clone(),
band: self.band.clone(),
weight: self.weight,
known: self.known,
}
}
}
pub fn trust_band_reliability(band: &str) -> Option<f32> {
match band {
"high" => Some(0.2), "medium" => Some(0.5), "low" => Some(0.8), "insufficient_evidence" => None, _ => None, }
}
pub fn binding_reliability(band: &str) -> Option<f32> {
match band {
"full_trust" => Some(1.0),
"needs_ingest" | "orientation_only" => Some(0.4),
"stale_binding_suspected" | "degraded" => Some(0.15),
_ => None,
}
}
pub fn weigh_factors(factors: &[FactorInput], cal_row: Option<&CalibrationRow>) -> TrustEnvelope {
let factor_receipts: Vec<TrustFactor> = factors.iter().map(FactorInput::to_factor).collect();
let mut numerator = 0.0f32;
let mut denominator = 0.0f32;
let mut known_count = 0usize;
for f in factors {
if !f.known {
continue;
}
if !f.weight.is_finite() || f.weight <= 0.0 || !f.reliability.is_finite() {
continue;
}
let v = f.reliability.clamp(0.0, 1.0);
numerator += f.weight * v;
denominator += f.weight;
known_count += 1;
}
if known_count == 0 || denominator <= f32::EPSILON {
return TrustEnvelope {
verdict: VERDICT_UNPROVABLE.to_string(),
score: 0.0,
calibrated: cal_row.is_some(),
factors: factor_receipts,
reasons: vec![
"no provable trust factor was available on this path — the answer is UNPROVABLE, not trusted; re-verify against local files".to_string(),
],
next_repair_call: first_repair_hint(factors),
};
}
let score = numerator / denominator;
if !score.is_finite() {
return TrustEnvelope {
verdict: VERDICT_UNPROVABLE.to_string(),
score: 0.0,
calibrated: cal_row.is_some(),
factors: factor_receipts,
reasons: vec![
"the weighted trust score was non-finite — reporting UNPROVABLE rather than a fabricated verdict".to_string(),
],
next_repair_call: first_repair_hint(factors),
};
}
let (verdict, calibrated) = match cal_row {
Some(row) => (
calibration::verdict_for(score, row.tau, row.tau_low()).to_string(),
true,
),
None => (calibration::VERDICT_REVERIFY.to_string(), false),
};
let mut reasons = Vec::new();
match calibrated {
true => reasons.push(format!(
"weighted trust score {score:.2} over {known_count} known factor(s), binned by the calibrated `envelope` threshold → {verdict}"
)),
false => reasons.push(format!(
"weighted trust score {score:.2} over {known_count} known factor(s), but the `envelope` signal is UNCALIBRATED — `act` is unreachable and the verdict is capped at `reverify` until a calibration row is measured"
)),
}
for f in factors {
if f.known && f.reliability.is_finite() && f.reliability < 0.5 {
reasons.push(format!(
"factor `{}` is weak (band `{}`, reliability {:.2})",
f.name, f.band, f.reliability
));
}
}
for f in factors {
if !f.known {
reasons.push(format!("factor `{}` deferred ({})", f.name, f.band));
}
}
let next_repair_call = if verdict == calibration::VERDICT_ACT {
None
} else {
weakest_repair_hint(factors).or_else(|| first_repair_hint(factors))
};
TrustEnvelope {
verdict,
score,
calibrated,
factors: factor_receipts,
reasons,
next_repair_call,
}
}
pub fn cheap_trust_mode_band(node_count: u64, edge_count: u64, finalized: bool) -> &'static str {
if node_count == 0 || edge_count == 0 || !finalized {
"needs_ingest"
} else {
"full_trust"
}
}
pub fn compose_seek_trust_envelope(
top_trust_bands: &[String],
binding_band: &str,
cal_row: Option<&CalibrationRow>,
) -> TrustEnvelope {
let mut factors: Vec<FactorInput> = Vec::new();
let worst = worst_trust_band(top_trust_bands);
match worst.as_deref().and_then(trust_band_reliability) {
Some(reliability) => factors.push(FactorInput {
name: FACTOR_TRUST_BAND.to_string(),
band: worst.unwrap_or_default(),
weight: WEIGHT_TRUST_BAND,
known: true,
reliability,
repair_hint: Some("cross_verify"),
}),
None => factors.push(FactorInput {
name: FACTOR_TRUST_BAND.to_string(),
band: worst
.map(|b| format!("deferred: {b}"))
.unwrap_or_else(|| "deferred: no results to band".to_string()),
weight: WEIGHT_TRUST_BAND,
known: false,
reliability: 0.0,
repair_hint: Some("cross_verify"),
}),
}
match binding_reliability(binding_band) {
Some(reliability) => factors.push(FactorInput {
name: FACTOR_BINDING.to_string(),
band: binding_band.to_string(),
weight: WEIGHT_BINDING,
known: true,
reliability,
repair_hint: Some(binding_repair(binding_band)),
}),
None => factors.push(FactorInput {
name: FACTOR_BINDING.to_string(),
band: format!("deferred: {binding_band}"),
weight: WEIGHT_BINDING,
known: false,
reliability: 0.0,
repair_hint: Some("trust_selftest"),
}),
}
for (name, probe) in DEFERRED_FACTORS {
factors.push(FactorInput {
name: (*name).to_string(),
band: format!("deferred: {probe}"),
weight: 1.0,
known: false,
reliability: 0.0,
repair_hint: None,
});
}
weigh_factors(&factors, cal_row)
}
const DEFERRED_FACTORS: &[(&str, &str)] = &[
(
"evidence_freshness",
"cross_verify (ingest-only, unavailable in seek)",
),
("am_i_stale", "am_i_stale (per-file I/O)"),
("closure", "why/impact closure (not built in seek)"),
("evidence_class", "mission_verify (needs an open mission)"),
];
fn worst_trust_band(bands: &[String]) -> Option<String> {
fn risk_rank(band: &str) -> u8 {
match band {
"low" => 0,
"medium" => 1,
"insufficient_evidence" => 2,
"high" => 3,
_ => 2,
}
}
bands.iter().max_by_key(|b| risk_rank(b)).cloned()
}
fn binding_repair(band: &str) -> &'static str {
match band {
"stale_binding_suspected" | "degraded" => "recovery_playbook",
"needs_ingest" => "ingest",
"orientation_only" => "trust_selftest",
_ => "trust_selftest",
}
}
fn weakest_repair_hint(factors: &[FactorInput]) -> Option<String> {
factors
.iter()
.filter(|f| f.known && f.reliability.is_finite() && f.repair_hint.is_some())
.min_by(|a, b| {
a.reliability
.partial_cmp(&b.reliability)
.unwrap_or(std::cmp::Ordering::Equal)
})
.and_then(|f| f.repair_hint.map(str::to_string))
}
fn first_repair_hint(factors: &[FactorInput]) -> Option<String> {
factors
.iter()
.find_map(|f| f.repair_hint.map(str::to_string))
}
#[cfg(test)]
mod tests {
use super::*;
fn cal_row() -> CalibrationRow {
CalibrationRow {
tau: 0.6,
target_alpha: calibration::DEFAULT_TARGET_ALPHA,
measured_precision: 0.85,
coverage: 0.4,
n: 100,
calibrated_at_ms: 1_700_000_000_000,
}
}
fn known(name: &str, reliability: f32, weight: f32) -> FactorInput {
FactorInput {
name: name.to_string(),
band: "band".to_string(),
weight,
known: true,
reliability,
repair_hint: Some("cross_verify"),
}
}
fn unknown(name: &str) -> FactorInput {
FactorInput {
name: name.to_string(),
band: "deferred: probe".to_string(),
weight: WEIGHT_TRUST_BAND,
known: false,
reliability: 0.0,
repair_hint: None,
}
}
#[test]
fn exact_score_and_act_verdict() {
let factors = [known("binding", 1.0, 1.0), known("trust_band", 0.8, 1.0)];
let env = weigh_factors(&factors, Some(&cal_row()));
assert_eq!(env.score, 0.90, "exact weighted score");
assert_eq!(env.verdict, "act");
assert!(env.calibrated);
assert!(env.next_repair_call.is_none(), "act ⇒ no repair call");
}
#[test]
fn unknown_factor_drops_out_of_both_sums() {
let clean_only = [known("trust_band", 0.8, 1.0)];
let clean_plus_unknown = [known("trust_band", 0.8, 1.0), unknown("closure")];
let a = weigh_factors(&clean_only, Some(&cal_row()));
let b = weigh_factors(&clean_plus_unknown, Some(&cal_row()));
assert_eq!(a.score, b.score, "unknown factor must not move the score");
assert_eq!(a.verdict, b.verdict);
assert_eq!(a.score, 0.80);
}
#[test]
fn single_red_factor_does_not_force_abstain() {
let factors = [
known("a", 1.0, 1.0),
known("b", 1.0, 1.0),
known("c", 1.0, 1.0),
known("red", 0.05, 1.0),
];
let env = weigh_factors(&factors, Some(&cal_row()));
assert!(
(env.score - 0.7625).abs() < 1e-6,
"weighted score should be 0.7625, got {}",
env.score
);
assert_eq!(
env.verdict, "act",
"one red factor must NOT force abstain when the weighted majority is clean"
);
}
#[test]
fn no_calibration_row_caps_at_reverify_never_act() {
let factors = [known("binding", 1.0, 1.0), known("trust_band", 1.0, 1.0)];
let env = weigh_factors(&factors, None);
assert_eq!(env.score, 1.0, "score still computed");
assert_eq!(env.verdict, "reverify", "uncalibrated ⇒ capped at reverify");
assert!(!env.calibrated);
assert_ne!(env.verdict, "act", "act is UNREACHABLE without calibration");
assert!(env.next_repair_call.is_some());
}
#[test]
fn all_unknown_is_unprovable() {
let factors = [unknown("trust_band"), unknown("binding")];
let env = weigh_factors(&factors, Some(&cal_row()));
assert_eq!(env.verdict, "unprovable");
assert_eq!(env.score, 0.0);
assert_ne!(env.verdict, "act");
}
#[test]
fn empty_factor_set_is_unprovable() {
let env = weigh_factors(&[], Some(&cal_row()));
assert_eq!(env.verdict, "unprovable");
assert_eq!(env.score, 0.0);
}
#[test]
fn nan_reliability_yields_unprovable_not_nan() {
let mut f = known("trust_band", f32::NAN, 1.0);
f.known = true;
let env = weigh_factors(&[f], Some(&cal_row()));
assert_eq!(env.verdict, "unprovable");
assert!(env.score.is_finite(), "score must never be NaN");
}
#[test]
fn zero_weight_factor_is_dropped() {
let env = weigh_factors(&[known("trust_band", 0.9, 0.0)], Some(&cal_row()));
assert_eq!(env.verdict, "unprovable");
}
#[test]
fn weak_score_abstains_with_repair() {
let env = weigh_factors(&[known("binding", 0.10, 1.0)], Some(&cal_row()));
assert_eq!(env.verdict, "abstain");
assert!(env.next_repair_call.is_some());
}
#[test]
fn borderline_score_reverifies() {
let env = weigh_factors(&[known("binding", 0.45, 1.0)], Some(&cal_row()));
assert_eq!(env.verdict, "reverify");
assert!(env.next_repair_call.is_some());
}
#[test]
fn band_maps_are_exact() {
assert_eq!(trust_band_reliability("high"), Some(0.2));
assert_eq!(trust_band_reliability("medium"), Some(0.5));
assert_eq!(trust_band_reliability("low"), Some(0.8));
assert_eq!(trust_band_reliability("insufficient_evidence"), None);
assert_eq!(trust_band_reliability("garbage"), None);
assert_eq!(binding_reliability("full_trust"), Some(1.0));
assert_eq!(binding_reliability("needs_ingest"), Some(0.4));
assert_eq!(binding_reliability("orientation_only"), Some(0.4));
assert_eq!(binding_reliability("stale_binding_suspected"), Some(0.15));
assert_eq!(binding_reliability("degraded"), Some(0.15));
assert_eq!(binding_reliability("garbage"), None);
}
}