affaan-m/ECC · error
candidate id collision with different immutable content
Error message
candidate id collision with different immutable content
What it means
After an INSERT ... ON CONFLICT(id) DO NOTHING into harness_candidates, the store re-reads the stored row and compares it byte-for-byte against the expected canonical config and ref JSON. If a row already existed under the candidate's v2 id with different immutable content, the write is treated as a content-address id collision and the call fails rather than silently overwriting.
Solutions
- Regenerate the candidate id from the current immutable content so a modified candidate gets a fresh id.
- Diff the stored canonical_config_json/trace_refs_json/evidence_refs_json against the submitted candidate to identify which field drifted, then make the content match the stored row.
- If the pre-existing row is wrong or abandoned, delete it explicitly and re-register the candidate.
- Ensure all writers use the same canonicalization (field order, JSON formatting) so identical content yields identical stored tuples.
Example fix
// before: content changed but id reused
let candidate = CandidateSpec { id: old_id, trace_refs: new_refs, .. };
store.persist_candidate(&candidate)?; // stored != expected -> collision
// after: derive id from current content
let id = candidate.id_for_v2()?; // fresh 64-char id reflecting new_refs
let candidate = CandidateSpec { id, trace_refs: new_refs, .. };
store.persist_candidate(&candidate)?; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_id_content_match(store: &Store, candidate: &CandidateSpec) -> anyhow::Result<()> {
if let Some(stored) = store.lookup_candidate(&candidate.id)? {
anyhow::ensure!(
stored.canonical_config_json == candidate.canonical_config
&& stored.trace_refs_json == serde_json::to_string(&candidate.trace_refs)?
&& stored.evidence_refs_json == serde_json::to_string(&candidate.evidence_refs)?,
"id already registered with different content"
);
}
Ok(())
} Prevention
- Always compute the v2 id via id_for_v2() from current content — never reuse ids across content changes
- Use one canonical JSON serialization everywhere so identical content yields identical stored tuples
- If registration fails, diff stored vs expected JSON to find the drifted field before retrying
When it happens
Trigger: Calling the candidate persist/registration API with a candidate whose 64-char v2 id already exists in harness_candidates but whose canonical_config_json, trace_refs_json, or evidence_refs_json differ from the stored tuple.
Common situations: A hash/id computation returning the same id for modified candidate content (e.g. after tweaking trace refs or evidence refs); two environments sharing one database with divergent candidate definitions under the same id; replaying an old registration after content changed.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- legacy candidate id collision with different immutable…
- an active harness configuration already exists
- atomic promotion compare-and-swap failed
- atomic rollback compare-and-swap failed
- baseline is not the active harness configuration
AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16).
Data as JSON: /api/errors/c20a24144414cc0e.
Report an issue: GitHub.
Appendix: 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 8321021c54)