{"record":{"id":"ee7712eedc32fa99","repo":"astrid-runtime/astrid","slug":"migration-ledger-contains-duplicate-component","errorCode":null,"errorMessage":"migration ledger contains duplicate component: {}","messagePattern":"migration ledger contains duplicate component: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":614,"sourceCode":"}\n\npub(super) fn canonical_json<T: Serialize>(value: &T) -> io::Result<Vec<u8>> {\n    let mut bytes = serde_json::to_vec(value).map_err(io::Error::other)?;\n    bytes.push(b'\\n');\n    Ok(bytes)\n}\n\n#[allow(\n    clippy::too_many_lines,\n    reason = \"all ledger invariants are checked before admission\"\n)]\npub(super) fn validate_ledger_shape(ledger: &MigrationLedger) -> io::Result<()> {\n    let mut names = std::collections::BTreeSet::new();\n    let mut previous = None;\n    for component in &ledger.components {\n        validate_component_name(&component.name)?;\n        if !names.insert(component.name.clone()) {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"migration ledger contains duplicate component: {}\",\n                    component.name\n                ),\n            ));\n        }\n        if previous\n            .as_ref()\n            .is_some_and(|previous: &String| previous >= &component.name)\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                \"migration ledger components are not canonically sorted\",\n            ));\n        }\n        previous = Some(component.name.clone());\n        if component.source.present && component.source.digest == \"absent\" {","sourceCodeStart":596,"sourceCodeEnd":632,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L596-L632","documentation":"`validate_ledger_shape` inserts each component name into a `BTreeSet` and fails if a name is inserted twice. This error is thrown when the ledger's `components` array contains two or more entries with the identical component name, which would make the migration state ambiguous (which entry's proof is authoritative?).","triggerScenarios":"Calling `validate_ledger_shape` (via `write_ledger` or the decode/validate paths like `reject_incomplete_layout_v2`, `retire_post_barrier_sources`, `resume_existing_layout`) on a ledger JSON where the same `name` appears in multiple component entries — typically from a botched manual merge or a duplicated array element.","commonSituations":"Merging two branches that both appended the same component to the ledger; a copy-paste duplication when hand-editing; a buggy external tool appending components without de-duplicating.","solutions":["De-duplicate the components array so each component name appears exactly once, keeping the entry whose `destination_proof` matches the on-disk receipts.","Prefer regenerating the ledger via the library (`write_ledger`) over hand-de-duplication, so proofs stay consistent with the filesystem.","If the duplication came from a Git merge, redo the merge resolving the ledger file by taking one side's component entry, not concatenating both.","After fixing, re-run the migration resume; the validation will confirm uniqueness."],"exampleFix":"// before\n\"components\": [\n  {\"name\": \"principal:01H8X:home\", \"source\": {...}, \"destination_proof\": \"A\"},\n  {\"name\": \"principal:01H8X:home\", \"source\": {...}, \"destination_proof\": \"B\"}\n]\n// after\n\"components\": [\n  {\"name\": \"principal:01H8X:home\", \"source\": {...}, \"destination_proof\": \"A\"}\n]","handlingStrategy":"validation","validationCode":"// Pre-flight: component names are unique before writing/validating\nfn no_duplicate_components(ledger: &MigrationLedger) -> bool {\n    let mut seen = std::collections::BTreeSet::new();\n    ledger.components.iter().all(|c| seen.insert(c.name.clone()))\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["De-duplicate by component name before appending entries in any external writer","Resolve ledger merge conflicts by choosing one entry per component, never concatenating","Keep the ledger append-only via the library so duplicates cannot be introduced","Validate generated ledgers with a dry-run resume before committing them"],"tags":["migration","validation","duplicates","rust"],"backgroundTag":"schema-validation-failed","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}