{"record":{"id":"816f57e8cc11ec0e","repo":"astrid-runtime/astrid","slug":"legacy-source-is-not-an-empty-regular-directory","errorCode":null,"errorMessage":"legacy source is not an empty regular directory: {}","messagePattern":"legacy source is not an empty regular directory: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":62,"sourceCode":"            sources,\n            format!(\"principal:{uid}:env:{capsule}\"),\n            home.principal_home(alias)\n                .env_dir()\n                .join(format!(\"{capsule}.env.json\")),\n        )?;\n        add_source(\n            sources,\n            format!(\"principal:{uid}:secret:{capsule}\"),\n            home.secrets_dir().join(alias.as_str()).join(capsule),\n        )?;\n    }\n    Ok(())\n}\n\npub(super) fn retire_empty_directory(path: &Path) -> io::Result<()> {\n    let metadata = fs::symlink_metadata(path)?;\n    if metadata.file_type().is_symlink() || !metadata.is_dir() {\n        return Err(io::Error::new(\n            io::ErrorKind::InvalidData,\n            format!(\n                \"legacy source is not an empty regular directory: {}\",\n                path.display()\n            ),\n        ));\n    }\n    astrid_core::platform_fs::verify_no_redirects(path)?;\n    fs::remove_dir(path).map_err(io::Error::other)?;\n    sync_parent(path)\n}\n\n/// Check every alias-keyed child of the released `secrets/` root.  The\n/// barrier passes `allow_empty_cleanup=false` while resuming a completed\n/// ledger, so a deleted or renamed principal cannot leave a reappeared empty\n/// directory that is silently swept on restart.\npub(super) fn ensure_legacy_secret_aliases(\n    root: &Path,","sourceCodeStart":44,"sourceCodeEnd":80,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L44-L80","documentation":"`retire_empty_directory` removes an empty legacy source directory only if it is a real, non-symlink directory. Anything else (symlink, file) is rejected with `InvalidData` to guarantee retirement never deletes through a redirect.","triggerScenarios":"Called from `ensure_legacy_secret_aliases`, `import_legacy_system_secrets`, or `handle_non_default_audit_source` when the path's `symlink_metadata` reports `is_symlink()` or not `is_dir()`.","commonSituations":"Legacy secret dir was symlinked to a dotfiles repo; the 'directory' was replaced by a file; a sync client substituted a placeholder file.","solutions":["Remove the symlink or file at the path and create/verify a real empty directory before migration.","Point the migration at the genuine legacy directory path.","Clean up the legacy layout so only real directories remain."],"exampleFix":"# before\nlegacy-secrets -> /mnt/backup/legacy-secrets\n# after\nrm legacy-secrets && mkdir legacy-secrets && copy files in","handlingStrategy":"validation","validationCode":"fn retire_ok(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p)\n        .map(|m| !m.file_type().is_symlink() && m.is_dir())\n        .unwrap_or(false)\n}","typeGuard":"fn is_plain_directory(p: &std::path::Path) -> bool {\n    std::fs::symlink_metadata(p).map(|m| m.is_dir() && !m.file_type().is_symlink()).unwrap_or(false)\n}","tryCatchPattern":"if is_plain_directory(legacy_dir) {\n    retire_empty_directory(legacy_dir)?;\n} else {\n    eprintln!(\"legacy dir is a symlink/file; normalize before migration\");\n}","preventionTips":["Normalize legacy directories (no symlinks) before running migration.","Check with `find legacy -type l` before migration.","Keep legacy roots out of symlink-managed dotfiles setups."],"tags":["filesystem","symlink","migration"],"backgroundTag":"incompatible-source-type","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}