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
- Ensure the CandidateSpec passed matches the originally stored legacy content byte-for-byte (same canonical_config, same trace/evidence refs).
- Pin and stabilize the canonical-JSON + hash algorithm used by id_for_v2 across versions.
- 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
- Pin the canonical-JSON and hash algorithm used by id_for_v2 across releases.
- Never change trace_refs/evidence_refs representation without a migration that re-derives legacy ids.
- Store the algorithm version alongside the legacy row so future drift is detectable.
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
- candidate id collision with different immutable content
- candidate alias collision with physical candidate id
- candidate alias collision with different immutable target
- an active harness configuration already exists
- baseline is not the active harness configuration
AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13).
Data as JSON: /api/errors/88a2b467a0d12843.
Report an issue: GitHub.