affaan-m/ECC · error

minimum mean delta must be finite

Error message

minimum mean delta must be finite

What it means

`PromotionPolicy::validate` requires `min_mean_delta` to be a finite number (not NaN or infinity). A non-finite threshold would make every comparison degenerate (always pass or always fail), so the policy is rejected.

Solutions

  1. Set `min_mean_delta` to a finite threshold (e.g. 0.05) before calling `compare`
  2. Replace NaN/inf sentinels with `Option<f64>` in your config layer and validate before constructing the policy
  3. Check the computation producing the threshold for division by zero or overflow

Example fix

// before
let policy = PromotionPolicy { min_mean_delta: f64::NAN, .. };
let comparison = policy.compare(&samples)?;
// after
let policy = PromotionPolicy { min_mean_delta: 0.05, .. };
let comparison = policy.compare(&samples)?;
Defensive patterns

Strategy: validation

Validate before calling

fn delta_threshold_is_valid(min_mean_delta: f64) -> bool {
    min_mean_delta.is_finite()
}

Type guard

fn finite_f64(v: f64) -> Option<f64> {
    if v.is_finite() { Some(v) } else { None }
}

Try / catch

match policy.compare(&samples) {
    Ok(comparison) => comparison,
    Err(e) if e.to_string().contains("mean delta must be finite") => {
        eprintln!("non-finite min_mean_delta in policy config: {e}");
        Err(anyhow::anyhow!("fix min_mean_delta in promotion config"))
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Building a `PromotionPolicy` with `min_mean_delta` set to `f64::NAN`, `f64::INFINITY`, or a value computed from a division by zero, then calling `compare` or `validate`.

Common situations: Thresholds read from config where a sentinel 'unset' value of NaN/inf is used; computing the delta threshold as `a / b` with `b == 0.0`; JSON deserialization of a float that overflowed.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/18211a4979d06eb4. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/harness_eval.rs:511

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Comparison {
    pub passed: bool,
    pub sample_count: usize,
    pub candidate_mean: f64,
    pub baseline_mean: f64,
    pub mean_delta: f64,
    pub win_rate: f64,
    pub failures: Vec<String>,
}

impl PromotionPolicy {
    pub fn validate(self) -> Result<()> {
        if self.min_samples == 0 {
            bail!("minimum samples must be positive");
        }
        if !self.min_mean_delta.is_finite() {
            bail!("minimum mean delta must be finite");
        }
        if !self.min_win_rate.is_finite() || !(0.0..=1.0).contains(&self.min_win_rate) {
            bail!("minimum win rate must be between 0 and 1");
        }
        Ok(())
    }

    pub fn compare(self, samples: &[PairedSample]) -> Result<Comparison> {
        self.validate()?;
        if samples.is_empty() {
            bail!("samples cannot be empty");
        }
        if samples
            .iter()
            .map(|sample| sample.seed)
            .collect::<BTreeSet<_>>()
            .len()
            != samples.len()

View on GitHub (pinned to 8321021c54)