affaan-m/ECC · error · anyhow::Error
candidate id collision with different immutable content
Error message
candidate id collision with different immutable content
What it means
A v2 candidate id is content-addressed. After INSERT ... ON CONFLICT(id) DO NOTHING the store reads back the persisted (canonical_config_json, trace_refs_json, evidence_refs_json) and compares to the expected triple. A mismatch means two different contents resolved to the same id — a hash collision is not expected, so this indicates corruption, a serialization bug, or a hash-function drift.
Source
Thrown at ecc2/src/session/store.rs:5394
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");
}
Ok(())
}
pub fn activate_initial_harness(&self, candidate_id: &str, evidence_ref: &str) -> Result<()> {
if candidate_id.len() != 64 || evidence_ref.trim().is_empty() || evidence_ref.len() > 4096 {
anyhow::bail!(
"valid candidate id and bounded activation evidence reference are required"
);
}
let tx = self.conn.unchecked_transaction()?;
let stored_candidate_id = Self::resolve_harness_candidate_id(&tx, candidate_id)?;
if tx
.query_row(
"SELECT candidate_id FROM active_harness_config WHERE slot = 'default'",
[],
|row| row.get::<_, String>(0),
)View on GitHub (pinned to 01e15490f0)
Solutions
- Guarantee candidate.id is computed from exactly the canonical_config/trace_refs/evidence_refs being stored (recompute and compare before insert).
- Use a canonical, deterministic JSON serializer (sorted keys, no extra whitespace) for all three fields before hashing.
- If the stored row is the wrong one, delete it and re-insert the correct triple.
Defensive patterns
Strategy: validation
Validate before calling
// Recompute the id from exactly what you are about to store, and confirm it is stable.
let expected_id = candidate.id_for_v2()?;
if expected_id != candidate.id {
return Err(anyhow::anyhow!("candidate.id does not match recomputed content id"));
}
// Build the expected stored triple with the SAME canonical serializer the store uses.
let expected = (candidate.canonical_config.clone(), trace_json.clone(), evidence_json.clone());
// (the store compares SELECT ... WHERE id = candidate.id against `expected`) Type guard
fn candidate_id_is_stable(candidate: &CandidateSpec) -> bool {
candidate.id_for_v2().ok().as_deref() == Some(candidate.id.as_str())
} Try / catch
match store.register_candidate(&candidate) {
Ok(()) => { /* ok */ }
Err(e) if e.to_string().contains("candidate id collision with different immutable content") => {
// two different contents hashed to the same id — investigate serialization/hash,
// do not retry with the same inputs.
}
Err(e) => return Err(e),
} Prevention
- Always compute candidate.id from the same canonical_config/trace_refs/evidence_refs being persisted, using a deterministic serializer (sorted keys, fixed whitespace).
- Unit-test that re-serializing a stored candidate reproduces the identical id.
- Never let two code paths compute the id with different rules.
When it happens
Trigger: register_candidate where candidate.id already exists in harness_candidates with different canonical_config_json/trace_refs_json/evidence_refs_json than the expected triple being inserted.
Common situations: JSON key ordering or whitespace changed between when candidate.id was hashed and when it is re-serialized for storage; two code paths compute candidate.id with different rules; a manual DB edit changed one column.
Related errors
- candidate alias collision with physical candidate id
- legacy candidate id collision with different immutable conte
- missing harness health evidence integrity metadata
- incomplete harness health evidence integrity metadata
- harness health evidence integrity verification failed
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/c20a24144414cc0e.
Report an issue: GitHub.