{"record":{"id":"88a2b467a0d12843","repo":"affaan-m/ECC","slug":"legacy-candidate-id-collision-with-different-immut","errorCode":null,"errorMessage":"legacy candidate id collision with different immutable content","messagePattern":"legacy candidate id collision with different immutable content","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"ecc2/src/session/store.rs","lineNumber":5376,"sourceCode":"                [&legacy_id],\n                |row| Ok((row.get::<_, String>(0)?, row.get::<_, String>(1)?, row.get::<_, String>(2)?)),\n            )\n            .optional()?;\n        let expected = (\n            candidate.canonical_config.clone(),\n            trace_json.clone(),\n            evidence_json.clone(),\n        );\n        if let Some(stored) = legacy {\n            let legacy_candidate = CandidateSpec {\n                id: legacy_id.clone(),\n                canonical_config: stored.0,\n                trace_refs: serde_json::from_str(&stored.1)?,\n                evidence_refs: serde_json::from_str(&stored.2)?,\n            };\n            legacy_candidate.verify_persisted_id(&legacy_id)?;\n            if legacy_candidate.id_for_v2()? != candidate.id {\n                anyhow::bail!(\"legacy candidate id collision with different immutable content\");\n            }\n            let tx = self.conn.unchecked_transaction()?;\n            Self::register_harness_alias(&tx, &candidate.id, &legacy_id)?;\n            tx.commit()?;\n            return Ok(());\n        }\n        self.conn.execute(\n            \"INSERT INTO harness_candidates (id, canonical_config_json, trace_refs_json, evidence_refs_json, created_at)\n             VALUES (?1, ?2, ?3, ?4, ?5) ON CONFLICT(id) DO NOTHING\",\n            rusqlite::params![candidate.id, candidate.canonical_config, trace_json, evidence_json, chrono::Utc::now().to_rfc3339()],\n        )?;\n        let stored: (String, String, String) = self.conn.query_row(\n            \"SELECT canonical_config_json, trace_refs_json, evidence_refs_json FROM harness_candidates WHERE id = ?1\",\n            [&candidate.id],\n            |row| Ok((row.get(0)?, row.get(1)?, row.get(2)?)),\n        )?;\n        if stored != expected {\n            anyhow::bail!(\"candidate id collision with different immutable content\");","sourceCodeStart":5358,"sourceCodeEnd":5394,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/ecc2/src/session/store.rs#L5358-L5394","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Before registering a legacy candidate, reconstruct the stored spec and confirm\n// its v2 id matches the candidate you are about to register.\nif let Some((cc, tr, er)) = stored_legacy_triple(&tx, &legacy_id)? {\n    let spec = CandidateSpec {\n        id: legacy_id.clone(),\n        canonical_config: cc,\n        trace_refs: serde_json::from_str(&tr)?,\n        evidence_refs: serde_json::from_str(&er)?,\n    };\n    if spec.id_for_v2()? != candidate.id {\n        return Err(anyhow::anyhow!(\"legacy content hash drift for {legacy_id}\"));\n    }\n}","typeGuard":"fn legacy_content_matches(spec: &CandidateSpec, candidate: &CandidateSpec) -> bool {\n    spec.id_for_v2().ok().as_deref() == candidate.id_for_v2().ok().as_deref()\n}","tryCatchPattern":"match register_legacy_candidate(&candidate, &legacy_id) {\n    Ok(()) => { /* ok */ }\n    Err(e) if e.to_string().contains(\"legacy candidate id collision\") => {\n        // content drifted under a fixed legacy id; do not blindly retry.\n        // Investigate hash/serialization stability, then re-register from the source of truth.\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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."],"tags":["rust","sqlite","harness","legacy","content-addressing"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}