{"record":{"id":"805aa62d905f0711","repo":"astrid-runtime/astrid","slug":"legacy-principal-home-source-detail","errorCode":null,"errorMessage":"legacy principal-home source {}: {detail}","messagePattern":"legacy principal-home source (.+?): (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/principal_home_migration/paths.rs","lineNumber":147,"sourceCode":"        [\".config\", name] => matches!(*name, \"profile.toml\" | \"distro.lock\" | \"distro.init.lock\"),\n        [\".local\", name, ..] => {\n            matches!(\n                *name,\n                \"capsules\" | \"audit\" | \"tmp\" | \"kv\" | \"tokens\" | \"log\"\n            )\n        },\n        _ => false,\n    }\n}\n\npub(super) fn storage_error(error: &FilesystemError) -> io::Error {\n    io::Error::other(format!(\n        \"authoritative home migration storage error: {error}\"\n    ))\n}\n\npub(super) fn invalid_source(path: &Path, detail: &str) -> io::Error {\n    io::Error::new(\n        io::ErrorKind::InvalidData,\n        format!(\"legacy principal-home source {}: {detail}\", path.display()),\n    )\n}\n\npub(super) fn conflict_path(path: &Path, detail: &str) -> io::Error {\n    io::Error::new(\n        io::ErrorKind::AlreadyExists,\n        format!(\n            \"principal-home migration conflict at {}: {detail}\",\n            path.display()\n        ),\n    )\n}\n\npub(super) fn conflict_fs(path: &FilesystemPath, detail: &str) -> io::Error {\n    io::Error::new(\n        io::ErrorKind::AlreadyExists,","sourceCodeStart":129,"sourceCodeEnd":165,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/principal_home_migration/paths.rs#L129-L165","documentation":"invalid_source builds the canonical 'legacy principal-home source {path}: {detail}' InvalidData error used throughout the principal-home migration. It is the generic rejection of a legacy source path that violates a migration precondition: non-canonical relative paths, non-UTF-8 names, over-long paths, non-canonical receipt UIDs, or non-UTF-8 entry names. The library throws it to fail closed before reading, snapshotting, retiring, or verifying legacy sources, since migrating a malformed source could corrupt the new home layout or receipts.","triggerScenarios":"Any of migrate_legacy_principal_homes, legacy_ordinary_source_snapshot, verify_migrated_legacy_principal_sources_retired, retire_one_receipted_source, retire_empty_tree, or walk_inventory encountering a legacy source path/receipt whose detail check fails — e.g. absolute or '..'-containing legacy relative paths (logical_relative), non-UTF-8 file names (append_relative), receipt file names whose UID portion does not parse canonically (receipt_uid_for_alias), or paths exceeding MAX_RELATIVE_PATH_BYTES.","commonSituations":"Legacy home trees produced by older versions with Windows-style or non-normalized paths; files created with non-UTF-8 byte names on permissive filesystems; hand-edited or copied migration receipt files in the migrations directory; extremely deep legacy directory trees exceeding the byte limit.","solutions":["Read the {detail} suffix in the error; it names the exact violated precondition (non-canonical, not UTF-8, too long, receipt UID not canonical).","Fix the offending legacy source at the printed path: rename to a canonical UTF-8 relative path or repair/remove the malformed receipt file.","Remove or restructure entries containing '..', absolute forms, or non-UTF-8 names under the legacy home, then re-run migration.","If a receipt is corrupt and no migration is in flight, delete the stale receipt so receipt_uid_for_alias no longer rejects it."],"exampleFix":"// before: non-UTF-8 name in legacy home\n$ rm './legacy/feff.txt'   # filename with invalid UTF-8 bytes\n\n// after: rename to a valid UTF-8 canonical name\n$ mv './legacy/feff.txt' './legacy/legacy-note.txt'\n$ # re-run the principal-home migration","handlingStrategy":"validation","validationCode":"fn validate_legacy_source(path: &std::path::Path) -> Result<(), String> {\n    if path.is_absolute() { return Err(\"absolute\".into()); }\n    if !path.components().all(|c| matches!(c, std::path::Component::Normal(_))) { return Err(\"non-canonical\".into()); }\n    let s = path.to_str().ok_or(\"not UTF-8\")?;\n    if s.replace('\\\\', \"/\").len() > MAX_RELATIVE_PATH_BYTES { return Err(\"too long\".into()); }\n    Ok(())\n}","typeGuard":"fn valid_receipt_name(name: &str) -> Option<(astrid_core::PrincipalUid, &str)> {\n    let uid_text = name.strip_prefix(RECEIPT_PREFIX)?.strip_suffix(RECEIPT_SUFFIX)?;\n    if uid_text.contains(RECEIPT_PAGE_MARKER) { return None; }\n    Some((uid_text.parse().ok()?, uid_text))\n}","tryCatchPattern":"match legacy_ordinary_source_snapshot(&source) {\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => {\n        let detail = e.to_string();\n        eprintln!(\"fix legacy source before migrating: {detail}\");\n        // surface detail (non-UTF-8 / too long / non-canonical) to the operator\n    }\n    other => other?,\n}","preventionTips":["Audit legacy trees with a scanner that rejects non-UTF-8 names and '..'/absolute components before migration day.","Never hand-edit migration receipt files; regenerate them via the library.","Keep legacy path depth/naming within MAX_RELATIVE_PATH_BYTES.","Use fs::read_dir result file_name().to_str() checks in your own tooling to catch bad names early."],"tags":["rust","io","path-validation","migration"],"backgroundTag":"invalid-argument-value","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"}