affaan-m/ECC · error

health evidence does not match candidate and evaluator

Error message

health evidence does not match candidate and evaluator

What it means

Before evaluating a promotion, the store verifies the supplied HealthEvidenceSnapshot (signature/integrity via verify()) and checks that its candidate_id and evaluator fields match the promotion call's arguments. A mismatch means the health snapshot attests a different candidate or evaluator than the promotion being attempted, so the operation is refused to keep evidence and action aligned.

Solutions

  1. Regenerate the HealthEvidenceSnapshot for the exact candidate_id and evaluator being promoted in this call.
  2. Compare health_evidence.candidate_id and health_evidence.evaluator against your arguments before calling to catch mismatches early.
  3. If promoting multiple candidates, keep snapshots keyed per (candidate_id, evaluator) rather than sharing one snapshot.
  4. Ensure verify() passes first; a failed verify indicates tampering/corruption and needs a fresh snapshot, not a field patch.

Example fix

// before: snapshot from previous run for another candidate
let snapshot = previous_snapshot; // candidate_id = "old..."
store.evaluate_promote_and_health_check(&new_cand, &base, "recorded-v1", ..., &snapshot, ...)?;

// after
let snapshot = HealthEvidenceSnapshot::build(&new_cand, "recorded-v1", asserted_healthy)?;
snapshot.verify()?;
store.evaluate_promote_and_health_check(&new_cand, &base, "recorded-v1", ..., &snapshot, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

// Verify snapshot alignment before calling the API
health_evidence.verify()?;
anyhow::ensure!(health_evidence.candidate_id == candidate_id, "snapshot is for a different candidate");
anyhow::ensure!(health_evidence.evaluator == evaluator, "snapshot is for a different evaluator");

Type guard

fn matches_promotion(snapshot: &HealthEvidenceSnapshot, candidate_id: &str, evaluator: &str) -> bool {
    snapshot.candidate_id == candidate_id && snapshot.evaluator == evaluator
}

Try / catch

match store.evaluate_promote_and_health_check(...) {
    Err(e) if e.to_string().contains("health evidence does not match") => {
        // rebuild the snapshot for this candidate/evaluator and retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling evaluate_promote_and_health_check with a health_evidence snapshot built for a different candidate_id or evaluator string than the arguments passed in, or with a snapshot that fails verify() (raising the verify error, not this one).

Common situations: Reusing a cached health snapshot from a previous evaluation of another candidate; swapping the evaluator label after the snapshot was created (e.g. changing "recorded-v1" casing or suffix); copy-paste mistakes when wiring multiple promotions in parallel.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at ecc2/src/session/store.rs:5476

        policy: PromotionPolicy,
        evidence_ref: &str,
        health_evidence: &HealthEvidenceSnapshot,
        health_check: F,
    ) -> Result<HarnessPromotionOutcome>
    where
        F: FnOnce(&str) -> Result<bool>,
    {
        if candidate_id.len() != 64
            || baseline_id.len() != 64
            || evaluator != "recorded-v1"
            || evidence_ref.trim().is_empty()
            || evidence_ref.len() > 4096
        {
            anyhow::bail!("valid candidate ids, recorded-v1 evaluator, and bounded evidence reference are required");
        }
        health_evidence.verify()?;
        if health_evidence.candidate_id != candidate_id || health_evidence.evaluator != evaluator {
            anyhow::bail!("health evidence does not match candidate and evaluator");
        }
        let comparison = policy.compare(samples)?;
        let tx = self.conn.unchecked_transaction()?;
        let stored_candidate_id = Self::resolve_harness_candidate_id(&tx, candidate_id)?;
        let stored_baseline_id = Self::resolve_harness_candidate_id(&tx, baseline_id)?;
        let active: String = tx
            .query_row(
                "SELECT candidate_id FROM active_harness_config WHERE slot = 'default'",
                [],
                |row| row.get(0),
            )
            .context("no active baseline configuration")?;
        if active != stored_baseline_id {
            anyhow::bail!("baseline is not the active harness configuration");
        }
        let now = chrono::Utc::now().to_rfc3339();
        if !comparison.passed {
            tx.execute("INSERT INTO harness_evaluations (candidate_id, baseline_id, evaluator, samples_json, policy_json, comparison_json, evidence_ref, legacy_unverifiable, created_at) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, 0, ?8)", rusqlite::params![stored_candidate_id, stored_baseline_id, evaluator, serde_json::to_string(samples)?, serde_json::to_string(&policy)?, serde_json::to_string(&comparison)?, evidence_ref, now])?;

View on GitHub (pinned to 8321021c54)