{"record":{"id":"03b5f6c7e842d19b","repo":"astrid-runtime/astrid","slug":"signed-distro-member-must-resolve-to-a-prebui","errorCode":null,"errorMessage":"signed Distro member '{}' must resolve to a prebuilt .capsule archive","messagePattern":"signed Distro member '(.+?)' must resolve to a prebuilt \\.capsule archive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/init_signed_source.rs","lineNumber":114,"sourceCode":"            .ok_or_else(|| {\n                anyhow::anyhow!(\"selected capsule '{}' is not in signed lock\", capsule.name)\n            })?;\n        let pinned_tag = signed.resolved_ref.as_deref().or(capsule.tag.as_deref());\n        let archive_path = staging.join(format!(\"{}.capsule\", capsule.name));\n        if let Some(local_source) =\n            resolve_local_capsule_archive(&capsule.source, bundle.manifest_path.as_deref())\n                .with_context(|| format!(\"resolve signed capsule {}\", capsule.name))?\n        {\n            std::fs::copy(&local_source, &archive_path).with_context(|| {\n                format!(\n                    \"copy signed capsule {} from {}\",\n                    capsule.name,\n                    local_source.display()\n                )\n            })?;\n        } else {\n            if capsule.source.starts_with('.') || capsule.source.starts_with('/') {\n                bail!(\n                    \"signed Distro member '{}' must resolve to a prebuilt .capsule archive\",\n                    capsule.name\n                );\n            }\n            let _ = Some(\n                super::super::capsule::install::resolve_capsule_to_file(\n                    &capsule.source,\n                    (!capsule.version.is_empty()).then_some(capsule.version.as_str()),\n                    pinned_tag,\n                    Some(&capsule.name),\n                    &archive_path,\n                )\n                .await?,\n            );\n        }\n        let bytes = std::fs::read(&archive_path)\n            .with_context(|| format!(\"read resolved capsule {}\", capsule.name))?;\n        let actual = manifest_hash(&bytes);","sourceCodeStart":96,"sourceCodeEnd":132,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/init_signed_source.rs#L96-L132","documentation":"resolve_signed_capsules requires every member of a signed Distro to resolve to a prebuilt .capsule archive. When a member's source is a relative path ('.') or absolute path ('/'), it is rejected outright because signed Distros only accept either local prebuilt archives or registry-resolvable names — path-style sources would bypass the signed-lock hash verification model.","triggerScenarios":"Calling resolve_signed_capsules with a Distro.toml member whose `source` field begins with '.' or '/', i.e. a relative or absolute filesystem path that is not a prebuilt .capsule archive.","commonSituations":"Hand-writing a Distro.toml and pointing a member at a source directory (e.g. source = \"./my-capsule\" or \"/opt/capsules/foo\") instead of a built .capsule file or a registry name; forgetting to run the capsule build step that produces the archive.","solutions":["Build the capsule first and reference the resulting prebuilt .capsule archive (path ending in .capsule) in Distro.toml.","If the member should come from the registry, remove the leading '.' or '/' so the name is resolved via resolve_capsule_to_file.","Verify each member's source field in Distro.toml: only registry names or prebuilt .capsule archive paths are allowed."],"exampleFix":"// before (Distro.toml)\n[[capsule]]\nname = \"my-tool\"\nsource = \"./my-tool\"\n\n// after\n[[capsule]]\nname = \"my-tool\"\nsource = \"./dist/my-tool.capsule\"","handlingStrategy":"validation","validationCode":"// pre-check Distro.toml member sources\nfor src in $(tomlq '.capsule[].source' Distro.toml); do\n  case \"$src\" in\n    .*|/*) [[ \"$src\" == *.capsule ]] || echo \"INVALID signed member source: $src\";;\n  esac\ndone","typeGuard":"fn is_valid_signed_source(source: &str) -> bool {\n    !(source.starts_with('.') || source.starts_with('/')) || source.ends_with(\".capsule\")\n}","tryCatchPattern":"// rust\nmatch resolve_signed_capsules(&capsules, &lock) {\n    Ok(members) => install(members),\n    Err(e) if e.to_string().contains(\"must resolve to a prebuilt .capsule archive\") => {\n        eprintln!(\"build capsules first: astrid capsule build --all\");\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Always run the capsule build step before signing a Distro.","Reference only registry names or prebuilt .capsule archives in signed Distro.toml.","Lint Distro.toml for path-style sources in CI."],"tags":["validation","config","capsules","signed-distro"],"backgroundTag":"invalid-argument-value","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"}