{"record":{"id":"b7c07a6a6bb2aa91","repo":"astrid-runtime/astrid","slug":"durable-capsule-id-is-missing-capsule-toml","errorCode":null,"errorMessage":"durable capsule {id} is missing Capsule.toml","messagePattern":"durable capsule (.+?) is missing Capsule\\.toml","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-capsule-install/src/storage.rs","lineNumber":169,"sourceCode":"/// prefer [`read_verified_durable_package`], which takes an immutable UID.\npub fn read_verified_durable_package_for_owner(\n    store: &RuntimePrincipalStore,\n    owner: &StateOwner,\n    id: &str,\n) -> anyhow::Result<Option<VerifiedDurableCapsulePackage>> {\n    let registry = store.capsules();\n    let Some(snapshot) = registry.get_snapshot(owner, id)? else {\n        return Ok(None);\n    };\n    let package = snapshot.package();\n    let verification = artifact::verify_archive_bytes(&package.archive)\n        .with_context(|| format!(\"verify durable capsule archive {id}\"))?;\n    let inventory = read_archive_files(&package.archive)?;\n    let ArchiveInventory { files, directories } = inventory;\n    let manifest_bytes = files\n        .get(\"Capsule.toml\")\n        .cloned()\n        .ok_or_else(|| anyhow::anyhow!(\"durable capsule {id} is missing Capsule.toml\"))?;\n    let manifest_text = String::from_utf8(manifest_bytes.clone())\n        .with_context(|| format!(\"durable capsule {id} manifest is not UTF-8\"))?;\n    let manifest: CapsuleManifest = toml::from_str(&manifest_text)\n        .with_context(|| format!(\"decode durable capsule {id} manifest\"))?;\n    let metadata: CapsuleMeta = serde_json::from_slice(&package.metadata)\n        .with_context(|| format!(\"decode durable capsule {id} metadata\"))?;\n    let authority: InstalledAuthority = serde_json::from_slice(&package.authority)\n        .with_context(|| format!(\"decode durable capsule {id} authority\"))?;\n    let metadata_bytes = package.metadata.clone();\n    verify_package_identity(\n        id,\n        &manifest,\n        &metadata,\n        &authority,\n        &manifest_bytes,\n        &verification,\n        &files,\n    )?;","sourceCodeStart":151,"sourceCodeEnd":187,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-capsule-install/src/storage.rs#L151-L187","documentation":"read_verified_durable_package_for_owner verifies the durable capsule's archive against its recorded checksum and then enumerates its files, requiring a top-level 'Capsule.toml' manifest entry. If the verified archive contains no Capsule.toml, the package is structurally invalid and this error is raised before any manifest parsing.","triggerScenarios":"Any caller (durable_contracts_pin, refresh_canonical_contracts_from_registry, durable_capsule_metadata, read_verified_durable_package, archive-entry tests, leftover matching) reads a durable capsule whose archive was built without the manifest at the archive root — e.g. the manifest ended up under a subdirectory like ./Capsule.toml or was omitted by the packaging tool.","commonSituations":"A hand-rolled tar built from the wrong directory so Capsule.toml is nested one level deep; packaging scripts that exclude dotfiles/manifests; archives migrated by an older tool version with a different layout.","solutions":["List the archive entries and confirm Capsule.toml sits at the archive root; rebuild with the package root as tar's working directory.","Re-run the directory publish (publish_directory_package / capsule install) so the archive is regenerated correctly from the capsule source.","Check packaging scripts/exclude patterns for rules that drop Capsule.toml.","If the capsule was migrated from an older format, re-migrate with the current tool version."],"exampleFix":"// before (from parent dir — manifest nested)\ntar -cf capsule.tar ./my-capsule\n// after (from package root — Capsule.toml at top level)\ncd my-capsule && tar -cf ../capsule.tar .","handlingStrategy":"validation","validationCode":"fn archive_has_root_manifest(tar_path: &std::path::Path) -> anyhow::Result<bool> {\n    let f = std::fs::File::open(tar_path)?;\n    let mut tar = tar::Archive::new(std::io::BufReader::new(f));\n    for entry in tar.entries()? {\n        let name = entry?.path()?.to_path_buf();\n        if name.file_name().map(|n| n == \"Capsule.toml\").unwrap_or(false)\n            && name.parent().map(|p| p.as_os_str().is_empty() || p == Path::new(\".\")).unwrap_or(true) {\n            return Ok(true);\n        }\n    }\n    Ok(false)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always create archives from inside the package root so Capsule.toml is at the top level.","Check packaging exclude patterns don't drop Capsule.toml.","Verify published archives with read_verified_durable_package before distributing."],"tags":["archive","manifest","missing-file"],"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-17T15:17:12.973Z"}