affaan-m/ECC · error · anyhow::Error

legacy candidate id collision with different immutable conte

Error message

legacy candidate id collision with different immutable content

What it means

Thrown while registering a legacy (pre-v2) candidate: a stored legacy row exists, but reconstructing its CandidateSpec and computing id_for_v2() yields an id different from the candidate.id being registered. Legacy slots are content-addressed and immutable, so a content mismatch under the same legacy id is corruption or a hash drift.

Source

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

                [&legacy_id],
                |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?)),
            )
            .optional()?;
        let expected = (
            candidate.canonical_config.clone(),
            trace_json.clone(),
            evidence_json.clone(),
        );
        if let Some(stored) = legacy {
            let legacy_candidate = CandidateSpec {
                id: legacy_id.clone(),
                canonical_config: stored.0,
                trace_refs: serde_json::from_str(&stored.1)?,
                evidence_refs: serde_json::from_str(&stored.2)?,
            };
            legacy_candidate.verify_persisted_id(&legacy_id)?;
            if legacy_candidate.id_for_v2()? != candidate.id {
                anyhow::bail!("legacy candidate id collision with different immutable content");
            }
            let tx = self.conn.unchecked_transaction()?;
            Self::register_harness_alias(&tx, &candidate.id, &legacy_id)?;
            tx.commit()?;
            return Ok(());
        }
        self.conn.execute(
            "INSERT INTO harness_candidates (id, canonical_config_json, trace_refs_json, evidence_refs_json, created_at)
             VALUES (?1, ?2, ?3, ?4, ?5) ON CONFLICT(id) DO NOTHING",
            rusqlite::params![candidate.id, candidate.canonical_config, trace_json, evidence_json, chrono::Utc::now().to_rfc3339()],
        )?;
        let stored: (String, String, String) = self.conn.query_row(
            "SELECT canonical_config_json, trace_refs_json, evidence_refs_json FROM harness_candidates WHERE id = ?1",
            [&candidate.id],
            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),
        )?;
        if stored != expected {
            anyhow::bail!("candidate id collision with different immutable content");

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Ensure the CandidateSpec passed matches the originally stored legacy content byte-for-byte (same canonical_config, same trace/evidence refs).
  2. Pin and stabilize the canonical-JSON + hash algorithm used by id_for_v2 across versions.
  3. If the stored legacy row is stale or corrupt, remove it and re-register from the authoritative source.
Defensive patterns

Strategy: validation

Validate before calling

// Before registering a legacy candidate, reconstruct the stored spec and confirm
// its v2 id matches the candidate you are about to register.
if let Some((cc, tr, er)) = stored_legacy_triple(&tx, &legacy_id)? {
    let spec = CandidateSpec {
        id: legacy_id.clone(),
        canonical_config: cc,
        trace_refs: serde_json::from_str(&tr)?,
        evidence_refs: serde_json::from_str(&er)?,
    };
    if spec.id_for_v2()? != candidate.id {
        return Err(anyhow::anyhow!("legacy content hash drift for {legacy_id}"));
    }
}

Type guard

fn legacy_content_matches(spec: &CandidateSpec, candidate: &CandidateSpec) -> bool {
    spec.id_for_v2().ok().as_deref() == candidate.id_for_v2().ok().as_deref()
}

Try / catch

match register_legacy_candidate(&candidate, &legacy_id) {
    Ok(()) => { /* ok */ }
    Err(e) if e.to_string().contains("legacy candidate id collision") => {
        // content drifted under a fixed legacy id; do not blindly retry.
        // Investigate hash/serialization stability, then re-register from the source of truth.
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: register legacy-candidate path where legacy_id already has a stored (canonical_config, trace_refs, evidence_refs) triple, verify_persisted_id passes, but legacy_candidate.id_for_v2()? != candidate.id.

Common situations: The stored legacy row was written under a different hash algorithm or canonical-JSON scheme; trace_refs/evidence_refs serialization order changed; the legacy row was partially overwritten.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/88a2b467a0d12843. Report an issue: GitHub.