{"record":{"id":"2cd531f4fe02dd7d","repo":"astrid-runtime/astrid","slug":"migration-ledger-components-are-not-canonically-so","errorCode":null,"errorMessage":"migration ledger components are not canonically sorted","messagePattern":"migration ledger components are not canonically sorted","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":626,"sourceCode":"pub(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\" {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"present migration source has absent digest: {}\",\n                    component.name\n                ),\n            ));\n        }\n        if !component.source.present && component.source.digest != \"absent\" {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\"absent migration source has a digest: {}\", component.name),","sourceCodeStart":608,"sourceCodeEnd":644,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L608-L644","documentation":"`validate_ledger_shape` requires the components array to be strictly sorted ascending by component name (`previous >= name` fails). This error is thrown when entries are out of lexicographic order or repeated adjacently, enforcing a canonical byte layout that makes the ledger deterministic and tamper-evident.","triggerScenarios":"Calling `validate_ledger_shape` (via `write_ledger`, `reject_incomplete_layout_v2`, `retire_post_barrier_sources`, `resume_existing_layout`, or the proof-related tests) on a ledger whose components were appended in insertion order rather than sorted by name — e.g. `system:fresh-layout` appearing after `principal:...` entries, or two entries out of order after a manual edit.","commonSituations":"Hand-merging ledger files and concatenating arrays without sorting; an external tool that appends new components at the end of the array; JSON tools that preserve original order after edits.","solutions":["Sort the components array lexicographically (byte-wise by component name) and ensure strictly unique names, then re-validate.","Best: rebuild the ledger through the library's `write_ledger`, which emits canonically sorted components.","If editing by hand, run a JSON-aware sort on the `name` key of the components array and verify with a resume call.","Fix the external writer to insert components in sorted position instead of appending."],"exampleFix":"// before\n\"components\": [\n  {\"name\": \"system:cow\", ...},\n  {\"name\": \"principal:01H8X:home\", ...},\n  {\"name\": \"system:state-db\", ...}\n]\n// after\n\"components\": [\n  {\"name\": \"principal:01H8X:home\", ...},\n  {\"name\": \"system:cow\", ...},\n  {\"name\": \"system:state-db\", ...}\n]","handlingStrategy":"validation","validationCode":"// Pre-flight: components strictly sorted ascending by name\nfn components_sorted(ledger: &MigrationLedger) -> bool {\n    ledger.components.windows(2).all(|w| w[0].name < w[1].name)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sort components by name before serializing in any external writer","Insert new components at their sorted position, never append at the end","Use the library's write_ledger which guarantees canonical ordering","Combine the sort check with the uniqueness check — both must hold strictly"],"tags":["migration","validation","canonicalization","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"}