astrid-runtime/astrid · error
distro must have at least one capsule with role = "uplink"…
Error message
distro must have at least one capsule with role = "uplink" (a frontend)
What it means
A distro must include at least one capsule whose role is "uplink", which serves as the frontend entry point. validate_manifest scans all capsule roles and bails if none is an uplink, since a distro without a frontend cannot route traffic.
Solutions
- Add `role = "uplink"` to the capsule that should act as the frontend.
- If a frontend capsule is missing entirely, add one to the manifest.
- Verify the role string is exactly "uplink" (no typos or extra whitespace).
Example fix
// before capsules.backend.role = "service" // after capsules.frontend.role = "uplink"
Defensive patterns
Strategy: validation
Validate before calling
fn ensure_uplink(capsules: &[Capsule]) -> Result<(), String> {
if capsules.iter().any(|c| c.role.as_deref() == Some("uplink")) {
Ok(())
} else {
Err("distro needs at least one role = \"uplink\" capsule".into())
}
} Type guard
fn is_uplink(c: &Capsule) -> bool {
c.role.as_deref() == Some("uplink")
} Try / catch
if let Err(e) = ensure_uplink(&manifest.capsules) {
eprintln!("distro invalid: {e}");
std::process::exit(1);
} Prevention
- Keep a frontend/uplink capsule in every distro template.
- Lint for the exact role string "uplink" (avoid typos like "up-link").
- When removing capsules, check no distro loses its only uplink.
When it happens
Trigger: Building/validating a distro manifest where every capsule's role is missing or set to something other than "uplink" (e.g. all backend/service capsules).
Common situations: Authoring a new distro from scratch with only backend capsules; renaming a frontend capsule's role to a custom value; deleting the frontend capsule without replacing it.
Understand the failure class
Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.
Related errors
- capsule ' ': distro capsules require a concrete released…
- invites.issuers is non-empty but invites.default-group is…
- invites.max-principals must be "unlimited" or a…
- branding.accent-color
- branding.icon is bytes — distros must not embed assets…
AI-assisted analysis of astrid-runtime/astrid@affd8760f4 (2026-09-09).
Data as JSON: /api/errors/9a1986ad19bb7ab0.
Report an issue: GitHub.
Appendix: source
Thrown at crates/astrid-cli/src/commands/distro/validate.rs:195
}
if matches!(tag, Some("")) {
anyhow::bail!("capsule '{}': tag must not be empty", cap.name);
}
if version.is_empty() && tag.is_none() {
anyhow::bail!(
"capsule '{}': distro capsules require a concrete released version or tag",
cap.name
);
}
}
// At least one uplink.
let has_uplink = manifest
.capsules
.iter()
.any(|c| c.role.as_deref() == Some("uplink"));
if !has_uplink {
anyhow::bail!("distro must have at least one capsule with role = \"uplink\" (a frontend)");
}
// Variable references in capsule env.
let defined_vars: HashSet<&str> = manifest.variables.keys().map(String::as_str).collect();
for cap in &manifest.capsules {
for (key, value) in &cap.env {
for var_ref in extract_variable_refs(value) {
if !defined_vars.contains(var_ref) {
anyhow::bail!(
"capsule '{}' env.{key} references undefined variable '{{{{ {var_ref} }}}}'",
cap.name,
);
}
}
}
}
// Invite policy — additive, so the rule is "if any field is set,View on GitHub (pinned to affd8760f4)