{"record":{"id":"1eb7930a86cd18b4","repo":"astrid-runtime/astrid","slug":"principal-migration-ledger-is-missing-the-ordinary","errorCode":null,"errorMessage":"principal migration ledger is missing the ordinary-home component for {alias}/{uid}","messagePattern":"principal migration ledger is missing the ordinary-home component for (.+?)/(.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs","lineNumber":130,"sourceCode":"        .get(\"audit:migrations:legacy-principal-home-v1\")\n        .await\n        .map_err(storage_io)?\n        .map_or_else(DestinationProof::absent, |bytes| {\n            DestinationProof::from_hashed_bytes(&bytes)\n        });\n    for (alias, uid) in directory.bindings() {\n        let home_component = format!(\"principal:{uid}:home\");\n        if !sources.contains_key(&home_component) {\n            // The ledger records only principals that existed at cut-over.\n            // Principals admitted later are ordinary v2 state and must not be\n            // mistaken for missing legacy-source inventory on every restart.\n            // A surviving ordinary-home receipt proves the UID did participate\n            // in migration, so omitting its ledger component still fails closed.\n            let receipt = home\n                .migrations_dir()\n                .join(format!(\"principal-home-{uid}.json\"));\n            if path_exists(&receipt)? {\n                return Err(io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"principal migration ledger is missing the ordinary-home component for {alias}/{uid}\"\n                    ),\n                ));\n            }\n            continue;\n        }\n        proofs.insert(\n            home_component,\n            destination_file_proof(\n                &home\n                    .migrations_dir()\n                    .join(format!(\"principal-home-{uid}.json\")),\n            )?,\n        );\n        proofs.insert(\n            format!(\"principal:{uid}:profile\"),","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/ledger.rs#L112-L148","documentation":"During proof collection, if an ordinary-home receipt file (`principal-home-{uid}.json`) still exists in the migrations directory but the corresponding ledger component for the principal's ordinary home is missing, this error is thrown. The surviving receipt proves the UID participated in migration, so an incomplete ledger would understate what was migrated — the code fails closed rather than resuming with an incomplete record.","triggerScenarios":"collect_destination_proofs (via resume_existing_layout, initialize_fresh_layout, migrate_legacy_layout, or existing_ledger_allows_principal_admitted_after_cutover) finds principal-home-{uid}.json on disk but no matching ordinary-home ledger component for {alias}/{uid}.","commonSituations":"The ledger file was partially restored or edited, dropping components while receipt files remained; a crashed/interrupted migration left receipts on disk with an older ledger version; manual cleanup deleted ledger entries but not the receipts directory.","solutions":["Check whether the ledger file was truncated or edited; restore it from a trusted backup.","Delete the stale receipt principal-home-{uid}.json only if the UID genuinely never participated in migration, then re-run.","Otherwise re-run the full migration from a consistent state so ledger and receipts are written together."],"exampleFix":"// before: stale receipt with no ledger component\n$ ls ~/.migrations/  # principal-home-1000.json\n// after: restore the ledger from backup, or remove the stale receipt and re-migrate\n$ rm ~/.migrations/principal-home-1000.json && astrid migrate","handlingStrategy":"validation","validationCode":"let receipt = migrations_dir.join(format!(\"principal-home-{uid}.json\"));\nif receipt.exists() && !ledger_has_component(&ledger, alias, uid) {\n    eprintln!(\"ledger/receipt mismatch for {alias}/{uid}; re-migrate\");\n}","typeGuard":null,"tryCatchPattern":"match resume_existing_layout(...) {\n    Err(e) if e.to_string().contains(\"missing the ordinary-home component\") => {\n        eprintln!(\"restore ledger from backup or remove stale receipt, then re-run\");\n    }\n    r => r?,\n}","preventionTips":["Restore ledger and receipts as a unit; never partially restore the migrations directory.","Don't delete ledger entries without also removing the corresponding principal-home-{uid}.json receipt.","Re-run migration after any interrupted run so ledger and receipts converge."],"tags":["security","ledger","migration","consistency"],"backgroundTag":"invalid-state-transition","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"}