{"record":{"id":"9f443169e9b4f22c","repo":"astrid-runtime/astrid","slug":"layout-cutover-record-is-not-a-regular-file","errorCode":null,"errorMessage":"layout cutover record is not a regular file: {}","messagePattern":"layout cutover record is not a regular file: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs","lineNumber":203,"sourceCode":"            stack.push(path);\n        }\n    }\n    Ok(targets)\n}\n\n/// A completed v2 ledger must be tied either to the explicit fresh-home\n/// disposition or to the durable layout cutover intent and receipt.  This\n/// check runs before stores open, so a canonical but invented component list\n/// cannot authorize the legacy layout finalizer.\npub(super) fn require_layout_provenance(migrations: &Path, fresh_layout: bool) -> io::Result<()> {\n    if fresh_layout {\n        return Ok(());\n    }\n    for name in [\"layout-v1-to-v2.intent\", \"layout-v1-to-v2.complete\"] {\n        let path = migrations.join(name);\n        let metadata = fs::symlink_metadata(&path).map_err(|error| {\n            if error.kind() == io::ErrorKind::NotFound {\n                io::Error::new(\n                    io::ErrorKind::InvalidData,\n                    format!(\n                        \"layout-two ledger has no durable cutover record: {}\",\n                        path.display()\n                    ),\n                )\n            } else {\n                error\n            }\n        })?;\n        if metadata.file_type().is_symlink() || !metadata.is_file() {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidData,\n                format!(\n                    \"layout cutover record is not a regular file: {}\",\n                    path.display()\n                ),\n            ));","sourceCodeStart":185,"sourceCodeEnd":221,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/legacy_migration_barrier/host_fs.rs#L185-L221","documentation":"A completed layout v1-to-v2 migration must be backed by the durable cutover records `layout-v1-to-v2.intent` and `layout-v1-to-v2.complete` in the migrations directory. require_layout_provenance found one of these records missing (or reachable only through a redirect) and rejects the ledger with InvalidData. This prevents a fabricated or partially restored migrations directory from authorizing the layout finalizer.","triggerScenarios":"Calling reject_incomplete_layout_v2 / require_layout_provenance with fresh_layout=false when either `layout-v1-to-v2.intent` or `layout-v1-to-v2.complete` is absent under the migrations directory (NotFound is remapped here), or when symlink_metadata of the record path itself fails. The distinct 'not a regular file' variant at line 218 fires when the record exists but is a symlink or non-file.","commonSituations":"Restoring a home directory from a partial backup that omitted the migrations dir; users hand-deleting migration bookkeeping files; a crashed first run that never wrote both records; pointing the app at the wrong (fresh) home while a v2 ledger exists elsewhere.","solutions":["Restore both `layout-v1-to-v2.intent` and `layout-v1-to-v2.complete` from a trusted backup of the migrations directory.","If this home never completed the v1-to-v2 migration, complete or redo the migration so the records are written durably.","Set the fresh-layout disposition explicitly if this is genuinely a brand-new home, so the records are not required.","Verify you are pointing at the correct home directory; a wrong ASTRID_HOME looks identical to missing records.","Never recreate the records by hand — they carry cryptographic provenance; hand-made copies will fail later digest checks."],"exampleFix":"// before\nmv ~/.astrid/migrations/layout-v1-to-v2.complete /tmp/  # hand-editing bookkeeping\n// after\n# restore from backup or rerun the migration to regenerate both records\ncp backup/migrations/layout-v1-to-v2.* ~/.astrid/migrations/","handlingStrategy":"validation","validationCode":"use std::fs;\nfn cutover_records_present(migrations: &std::path::Path) -> std::io::Result<bool> {\n    for name in [\"layout-v1-to-v2.intent\", \"layout-v1-to-v2.complete\"] {\n        match fs::symlink_metadata(migrations.join(name)) {\n            Ok(_) => {}\n            Err(e) if e.kind() == std::io::ErrorKind::NotFound => return Ok(false),\n            Err(e) => return Err(e),\n        }\n    }\n    Ok(true)\n}","typeGuard":null,"tryCatchPattern":"if let Err(e) = require_layout_provenance(migrations, fresh_layout) {\n    if e.to_string().contains(\"no durable cutover record\") {\n        // restore records from backup or rerun the migration\n        return Err(MigrationError::MissingProvenance);\n    }\n    return Err(e.into());\n}","preventionTips":["Back up the entire migrations directory, not just data directories","Never delete or move files under migrations/ by hand","Confirm ASTRID_HOME points at the intended home before upgrading"],"tags":["filesystem","migration","provenance","rust"],"backgroundTag":"file-not-found","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"}