{"record":{"id":"59d7e76a5d98268a","repo":"astrid-runtime/astrid","slug":"capsule-distro-capsules-require-a-concrete-r","errorCode":null,"errorMessage":"capsule '{}': distro capsules require a concrete released version or tag","messagePattern":"capsule '(.+?)': distro capsules require a concrete released version or tag","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":182,"sourceCode":"                \"capsule '{}': version '{}' is not valid semver\",\n                cap.name,\n                cap.version\n            );\n        }\n        let tag = cap.tag.as_deref().map(str::trim);\n        if let Some(raw_tag) = cap.tag.as_deref()\n            && raw_tag != raw_tag.trim()\n        {\n            anyhow::bail!(\n                \"capsule '{}': tag must not contain surrounding whitespace\",\n                cap.name\n            );\n        }\n        if matches!(tag, Some(\"\")) {\n            anyhow::bail!(\"capsule '{}': tag must not be empty\", cap.name);\n        }\n        if version.is_empty() && tag.is_none() {\n            anyhow::bail!(\n                \"capsule '{}': distro capsules require a concrete released version or tag\",\n                cap.name\n            );\n        }\n    }\n\n    // At least one uplink.\n    let has_uplink = manifest\n        .capsules\n        .iter()\n        .any(|c| c.role.as_deref() == Some(\"uplink\"));\n    if !has_uplink {\n        anyhow::bail!(\"distro must have at least one capsule with role = \\\"uplink\\\" (a frontend)\");\n    }\n\n    // Variable references in capsule env.\n    let defined_vars: HashSet<&str> = manifest.variables.keys().map(String::as_str).collect();\n    for cap in &manifest.capsules {","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L164-L200","documentation":"This error comes from distro manifest validation in astrid-cli. A distro capsule with a 'release' selector must pin a concrete released artifact — either a version string or a git tag. If the capsule provides neither (empty version and no tag), the manifest is ambiguous and validate_manifest refuses it.","triggerScenarios":"Calling `astrid distro` validation/build on a manifest where a capsule's release selector has version: \"\" (empty) and tag: null/absent. Also produced as a side-effect when tests validate capsules whose selectors only use path/branch/rev (those are rejected elsewhere, but a release capsule with neither version nor tag hits this bail).","commonSituations":"Hand-written distro.toml/manifest where the author left `version = \"\"` as a placeholder, forgot to fill in the version after copying a capsule block, or intended a branch/dev checkout but left the release selector shape in place.","solutions":["Set a concrete released version on the capsule's release selector (e.g. version = \"1.2.0\").","Or set a tag: instead of version: on the capsule.","If the capsule should track a dev/source tree, use a path or branch selector instead of a release selector.","Run validation again to confirm all capsules now pin a release."],"exampleFix":"// before\ncapsules.myapi.version = \"\"\n\n// after\ncapsules.myapi.version = \"1.4.2\"","handlingStrategy":"validation","validationCode":"fn validate_release_selector(version: &str, tag: Option<&str>) -> Result<(), String> {\n    if version.is_empty() && tag.is_none() {\n        return Err(\"release capsule needs a version or tag\".into());\n    }\n    if tag == Some(\"\") {\n        return Err(\"tag must not be empty\".into());\n    }\n    Ok(())\n}","typeGuard":"fn has_release_pin(version: &str, tag: Option<&str>) -> bool {\n    !version.is_empty() || tag.map(|t| !t.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match validate_release_selector(&cap.version, cap.tag.as_deref()) {\n    Ok(()) => build(cap),\n    Err(e) => eprintln!(\"capsule '{}': {e}\", cap.name),\n}","preventionTips":["Never leave version = \"\" placeholders in committed manifests.","CI-lint manifests for empty version fields before build.","Prefer path/branch selectors for dev capsules so release selectors always carry a pin."],"tags":["manifest","validation","configuration"],"backgroundTag":"missing-required-config-field","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"}