{"record":{"id":"c13455cba18a474e","repo":"astrid-runtime/astrid","slug":"read-materialized-capsule-member-relative-erro","errorCode":null,"errorMessage":"read materialized capsule member {relative}: {error}","messagePattern":"read materialized capsule member (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/capsule_materialization.rs","lineNumber":96,"sourceCode":"            .collect::<std::collections::BTreeSet<_>>();\n        let archive_directories = verified\n            .archive_directories()\n            .map(ToOwned::to_owned)\n            .collect::<Vec<_>>();\n        expected_directories.extend(archive_directories.iter().cloned());\n        expected_directories.extend(\n            archive_directories\n                .iter()\n                .map(String::as_str)\n                .flat_map(authenticated_ancestor_directories),\n        );\n        if actual.directories != expected_directories {\n            anyhow::bail!(\"materialized capsule directory inventory differs from durable package\");\n        }\n        for (relative, expected) in &expected_files {\n            let materialized =\n                Self::read_projection_file_nofollow(&dir.join(relative)).map_err(|error| {\n                    anyhow::anyhow!(\"read materialized capsule member {relative}: {error}\")\n                })?;\n            if materialized != *expected {\n                anyhow::bail!(\n                    \"materialized capsule member {relative} differs from durable archive\"\n                );\n            }\n        }\n        let expansions = manifest\n            .capabilities\n            .expansions_from(&verified.authority().approved_capabilities);\n        if !expansions.is_empty() {\n            anyhow::bail!(\"materialized capsule manifest exceeds durable authority approval\");\n        }\n        Ok(())\n    }\n\n    /// Bind durable activation or authorize the explicit workspace portal.\n    #[cfg(not(all(target_arch = \"wasm32\", target_os = \"unknown\")))]","sourceCodeStart":78,"sourceCodeEnd":114,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/capsule_materialization.rs#L78-L114","documentation":"After checking the projection's file and directory inventory, verify_published_materialization reads every archive member of the capsule at its materialized path with read_projection_file_nofollow; any I/O failure is wrapped as 'read materialized capsule member {relative}'. The library throws it because a published capsule is only trustworthy when every member can be read and compared byte-for-byte against the durable archive. This is an I/O wrapper — a byte difference produces the separate 'differs from durable archive' error. The offending member path is included in the message.","triggerScenarios":"verify_published_materialization (directly or via repair_published_materialization, confirm_published_materialization, verify_registry_materialization) iterates expected_files and read_projection_file_nofollow fails for a member: the file is missing from the projection, is a symlink (nofollow refuses), or cannot be opened/read due to permissions or IO errors.","commonSituations":"A capsule cache partially deleted or truncated by cleanup tooling; files extracted without preserving expected layout; a member path replaced by a symlink (e.g. malicious or accidental); per-file permission differences after restoring a cache from a backup; read errors on a full or failing disk.","solutions":["Delete the affected materialization directory and re-materialize from the durable snapshot (repair_published_materialization / ensure_published_materialization).","Inspect the named member in the message: ensure it exists, is a regular file, and is readable (ls -la <dir>/<relative>).","Restore permissions/ownership on the cache tree to the principal's UID owner.","Check for symlinked members or external interference in the cache dir and re-extract cleanly; check disk health/space if read errors persist."],"exampleFix":"// before: failing on a cache dir missing archive members\nlet manifest = kernel.load_capsule(&dir, &principal, &manifest_for_discovery)?;\n\n// after: force repair of the incomplete projection first\nlet snapshot = kernel.published_capsule_snapshot(&principal, &manifest_for_discovery)?;\nlet target = kernel.published_cache_target(&principal, &manifest_for_discovery, &snapshot)?;\nlet bound = kernel.repair_published_materialization(&target, &principal, &manifest_for_discovery, &snapshot)?;","handlingStrategy":"validation","validationCode":"fn all_members_readable(dir: &std::path::Path, relatives: &[&str]) -> Result<(), String> {\n    for rel in relatives {\n        let p = dir.join(rel);\n        let md = std::fs::symlink_metadata(&p)\n            .map_err(|e| format!(\"{rel}: {e}\"))?;\n        if md.file_type().is_symlink() { return Err(format!(\"{rel}: symlink not allowed\")); }\n        std::fs::File::open(&p).map_err(|e| format!(\"{rel}: {e}\"))?;\n    }\n    Ok(())\n}","typeGuard":"fn projection_complete(dir: &std::path::Path, relatives: &[&str]) -> bool {\n    relatives.iter().all(|rel| {\n        std::fs::symlink_metadata(dir.join(rel))\n            .map(|md| md.is_file())\n            .unwrap_or(false)\n    })\n}","tryCatchPattern":"match kernel.load_capsule(&dir, &principal, &manifest) {\n    Ok(bound) => /* proceed */,\n    Err(e) if e.to_string().starts_with(\"read materialized capsule member \") => {\n        let member = e.to_string(); // parse relative path from message if needed\n        let _ = std::fs::remove_dir_all(&dir);      // rebuild torn projection\n        let bound = kernel.load_capsule(&dir, &principal, &manifest)?;\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always materialize via the library (materialize_capsule_package / repair paths) instead of copying files manually.","Prefer repair_published_materialization over ad-hoc cache surgery — it rebuilds torn projections from the durable snapshot.","Check disk space and health; partial extractions commonly stem from ENOSPC.","Avoid tools that convert cache files to symlinks (dedup/COW dedupers) — nofollow reads will refuse them."],"tags":["io","filesystem","capsule","materialization"],"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"}