{"record":{"id":"84f478ed14adde5c","repo":"astrid-runtime/astrid","slug":"materialized-capsule-directory-inventory-differs-f","errorCode":null,"errorMessage":"materialized capsule directory inventory differs from durable package","messagePattern":"materialized capsule directory inventory differs from durable package","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-kernel/src/capsule_materialization.rs","lineNumber":91,"sourceCode":"            anyhow::bail!(\"materialized capsule file inventory differs from durable package\");\n        }\n        let mut expected_directories = expected_files\n            .keys()\n            .flat_map(|relative| authenticated_ancestor_directories(relative))\n            .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        }","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-kernel/src/capsule_materialization.rs#L73-L109","documentation":"During published-materialization verification, the kernel inventories every directory present on disk under the materialized capsule cache and compares it to the set of directories implied by the durable registry package (archive directories plus ancestor directories of each archived file). This bail fires when the on-disk directory set differs from that expected set, i.e. the projected cache has extra, missing, or renamed directories relative to the immutable package snapshot. It exists to catch tampering, partial extraction, or stale generations of the cache rather than trusting the filesystem layout.","triggerScenarios":"verify_published_materialization is called by repair_published_materialization, confirm_published_materialization, or verify_registry_materialization, and Self::inventory_projection_files(dir) returns a `directories` BTreeSet that is not exactly equal to the set built from verified archive directories plus authenticated_ancestor_directories of every expected file path — e.g. an empty directory left behind by a prior generation, a deleted subdirectory, or a symlinked/renamed folder.","commonSituations":"A previous capsule version's cache was only partially removed before a new generation was projected; a user or tooling manually added or deleted directories inside the cache path; a backup/sync tool (or interrupted process) left extra empty directories; the package snapshot in the durable registry was republished with a different directory layout while the old projection remained on disk.","solutions":["Let the library repair the projection instead of fixing files by hand: call repair_published_materialization (or the load/ensure path that routes through it), which detects the failed verification, removes the stale tree with remove_dir_all, and re-materializes from the durable snapshot.","Delete the capsule's materialized cache directory for that principal/package and re-run the load so it is re-materialized from the registry snapshot.","Check that nothing external (editors, sync clients, cleanup scripts) mutates the cache directory between runs, then re-materialize.","If the difference is intentional, verify the durable package snapshot is the correct publication; a mismatched snapshot id (checked earlier as 'snapshot differs from caller's publication') means the caller passed the wrong CapsulePackageSnapshot."],"exampleFix":"// before: hand-editing or reusing a stale cache directory\nlet manifest = kernel.load_capsule(&cached_dir, &principal)?;\n\n// after: force repair/re-materialization from the durable snapshot\nlet manifest = kernel.repair_published_materialization(\n    &runtime_dir,\n    &principal,\n    &discovery_manifest,\n    &snapshot,\n)?;","handlingStrategy":"try-catch","validationCode":"// Pre-check directory inventory before calling the API\nlet expected_dirs: BTreeSet<String> = snapshot_archive_directories()\n    .into_iter()\n    .chain(expected_files.iter().flat_map(|p| ancestor_dirs(p)))\n    .collect();\nlet actual_dirs = list_directories(&cache_dir)?;\nif actual_dirs != expected_dirs {\n    // schedule a repair instead of load\n    kernel.repair_published_materialization(&cache_dir, &principal, &manifest, &snapshot)?;\n}","typeGuard":"fn is_plain_directory(path: &Path) -> bool {\n    std::fs::symlink_metadata(path)\n        .map(|m| m.is_dir())\n        .unwrap_or(false)\n}","tryCatchPattern":"match kernel.load_capsule(&cache_dir, &principal) {\n    Ok(m) => m,\n    Err(e) if e.to_string().contains(\"directory inventory differs\") => {\n        kernel.repair_published_materialization(&cache_dir, &principal, &manifest, &snapshot)?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Never create, rename, or delete directories inside the materialized capsule cache manually.","Exclude cache directories from backup/sync tools (Dropbox, Nextcloud, dotfile managers).","Route every cache-mismatch through repair_published_materialization instead of hand-patching.","Keep cache directories on local disk, not on network filesystems prone to partial updates."],"tags":["integrity","filesystem","cache-invalidation","security"],"backgroundTag":"checksum-mismatch","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"}