{"record":{"id":"6f1cb2b771a0817b","repo":"astrid-runtime/astrid","slug":"read-materialized-capsule-metadata-error","errorCode":null,"errorMessage":"read materialized capsule metadata: {error:#}","messagePattern":"read materialized capsule metadata: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/capsule_materialization.rs","lineNumber":44,"sourceCode":"            &astrid_storage::StateOwner::Principal(uid),\n            manifest.package.name.as_str(),\n        )?\n        .ok_or_else(|| anyhow::anyhow!(\"materialized capsule is absent from durable registry\"))?;\n        if verified.snapshot() != snapshot {\n            anyhow::bail!(\"materialized capsule snapshot differs from the caller's publication\");\n        }\n        if verified.manifest().package.name != manifest.package.name\n            || verified.manifest().package.version != manifest.package.version\n        {\n            anyhow::bail!(\"materialized capsule manifest differs from durable registry\");\n        }\n        let manifest_bytes = Self::read_projection_file_nofollow(&dir.join(\"Capsule.toml\"))\n            .map_err(|error| anyhow::anyhow!(\"read materialized capsule manifest: {error:#}\"))?;\n        if manifest_bytes != verified.manifest_bytes() {\n            anyhow::bail!(\"durable capsule manifest bytes do not match materialization\");\n        }\n        let metadata_bytes = Self::read_projection_file_nofollow(&dir.join(\"meta.json\"))\n            .map_err(|error| anyhow::anyhow!(\"read materialized capsule metadata: {error:#}\"))?;\n        if metadata_bytes != verified.metadata_bytes() {\n            anyhow::bail!(\"durable capsule metadata does not match materialization\");\n        }\n        let authority_bytes = Self::read_projection_file_nofollow(&dir.join(\"authority.json\"))\n            .map_err(|error| anyhow::anyhow!(\"read materialized capsule authority: {error:#}\"))?;\n        if authority_bytes != verified.snapshot().package().authority {\n            anyhow::bail!(\"durable capsule authority bytes do not match materialization\");\n        }\n        let mut expected_files = verified\n            .archive_entries()\n            .map(|(path, bytes)| (path.to_owned(), bytes.to_vec()))\n            .collect::<std::collections::BTreeMap<_, _>>();\n        expected_files.insert(\n            \"Capsule.toml\".to_owned(),\n            verified.manifest_bytes().to_vec(),\n        );\n        expected_files.insert(\"meta.json\".to_owned(), verified.metadata_bytes().to_vec());\n        expected_files.insert(","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/capsule_materialization.rs#L26-L62","documentation":"During verification of a published capsule materialization, the kernel re-reads the projected meta.json file with read_projection_file_nofollow and wraps any I/O failure with this message. The library throws it because a durable capsule is only valid when every projection file (Capsule.toml, meta.json, authority.json, and archive members) can be read byte-for-byte without following symlinks; if meta.json cannot be read, integrity cannot be proven. It is an I/O-error wrapper, not a content mismatch (mismatches produce separate 'does not match' bail errors).","triggerScenarios":"verify_published_materialization is called (directly or via repair_published_materialization, confirm_published_materialization, or verify_registry_materialization) while the file <materialization_dir>/meta.json is missing, unreadable, is a symlink (nofollow open refuses), or the process lacks read permission on it. Typical call sites are capsule load/prepare-runtime-replacement flows that repair or confirm the published cache.","commonSituations":"A partially completed or interrupted materialization left the cache directory without meta.json; an external tool or user deleted files from the capsule cache; a symlink attack or accidental symlink replaced meta.json; permissions were tightened on the cache directory; disk/IO errors while reading a network-mounted cache.","solutions":["Delete the stale materialization directory and let the kernel re-materialize it (e.g. call repair_published_materialization / ensure_published_materialization, which removes and republishes the projection).","Check that meta.json exists and is a regular file (not a symlink) inside the materialization dir: ls -la <dir>/meta.json.","Fix filesystem permissions so the process (or the principal's UID owner) can read the file.","Re-install or re-publish the capsule so the durable package and projection are regenerated from the snapshot."],"exampleFix":"// before: trusting an existing cache dir that may be incomplete\nlet bound = kernel.load_capsule(&dir, &principal, &manifest)?;\n\n// after: route through repair so a broken projection is rebuilt\nlet snapshot = kernel.published_capsule_snapshot(&principal, &manifest)?;\nlet target = kernel.published_cache_target(&principal, &manifest, &snapshot)?;\nlet bound_manifest = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;","handlingStrategy":"validation","validationCode":"fn meta_projection_readable(dir: &std::path::Path) -> Result<(), String> {\n    let p = dir.join(\"meta.json\");\n    let md = std::fs::symlink_metadata(&p)\n        .map_err(|e| format!(\"meta.json unreadable: {e}\"))?;\n    if md.file_type().is_symlink() { return Err(\"meta.json is a symlink\".into()); }\n    if !md.is_file() { return Err(\"meta.json is not a regular file\".into()); }\n    std::fs::File::open(&p).map_err(|e| format!(\"meta.json open failed: {e}\"))?;\n    Ok(())\n}","typeGuard":"fn has_regular_file(dir: &std::path::Path, name: &str) -> bool {\n    std::fs::symlink_metadata(dir.join(name))\n        .map(|md| md.is_file())\n        .unwrap_or(false)\n}","tryCatchPattern":"match kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot) {\n    Ok(m) => /* use m */,\n    Err(e) if e.to_string().contains(\"read materialized capsule metadata\") => {\n        let _ = std::fs::remove_dir_all(&target); // drop broken projection\n        let m = kernel.ensure_published_materialization(&target, &principal, &manifest, &snapshot)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never manually delete or edit files inside a materialized capsule cache; always re-materialize.","Before load/confirm, check Capsule.toml, meta.json and authority.json all exist as regular, non-symlink files.","Keep cache directory permissions aligned with the principal UID that owns the materialization.","Exclude capsule cache directories from cleanup tools and backup/restore tools that alter file metadata or symlinks."],"tags":["io","filesystem","capsule","materialization","security"],"backgroundTag":"file-read-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"}