unicity-aos/aos-ce · error · std::io::Error

embedded capsule has no source

Error message

embedded capsule has no source

What it means

For each `[[capsule]]` entry, capsule_assets_from_manifest reads the `source` field as a string, which gives the path of the capsule asset relative to the distro. This error is thrown when an entry lacks `source` or the value is not a string. Without it the code cannot locate or validate the embedded asset.

Solutions

  1. Add a string `source` field to the offending `[[capsule]]` entry, e.g. `source = "capsules/<package>.capsule"`.
  2. Ensure the value is a single TOML string, not an array or table.
  3. Make the source path canonical (see error 18): exactly `capsules/<name>.capsule` relative to the distro root.
  4. Verify with the manifest-producing build script that every capsule entry carries both name and source.

Example fix

// before
[[capsule]]
name = "foo"

// after
[[capsule]]
name = "foo"
source = "capsules/foo.capsule"
Defensive patterns

Strategy: validation

Validate before calling

// Rust: check every capsule entry has a string source before calling the API
fn ensure_capsule_sources(manifest_toml: &str) -> Result<(), String> {
    let v: toml::Value = toml::from_str(manifest_toml).map_err(|e| e.to_string())?;
    for (i, c) in v.get("capsule").and_then(toml::Value::as_array).unwrap_or(&vec![]).iter().enumerate() {
        if c.get("source").and_then(toml::Value::as_str).is_none() {
            return Err(format!("capsule entry #{i} is missing a string `source`"));
        }
    }
    Ok(())
}

Type guard

fn capsule_source(capsule: &toml::Value) -> Option<&str> {
    capsule.get("source").and_then(toml::Value::as_str)
}

Try / catch

match prepare_unicity_ce_init(...) {
    Err(e) if e.to_string().contains("embedded capsule has no source") => {
        eprintln!("a [[capsule]] entry lacks a string `source`; add `source = \"capsules/<pkg>.capsule\"`");
    }
    other => other,
}

Prevention

When it happens

Trigger: A `[[capsule]]` entry in UNICITY_CE_MANIFEST omits `source`, or `source` is a non-string TOML value. Raised by capsule_assets_from_manifest when called via capsule_dir_with, prepare_unicity_ce_init, install_capsule_fixtures, or the serialization test.

Common situations: Adding a new capsule entry and forgetting the source path; a manifest generator emitting only `name`; renaming `source` to something like `path` during a refactor; typing `source` as an array of paths for multi-asset capsules.

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


AI-assisted analysis of unicity-aos/aos-ce@f6f22024fb (2026-09-13). Data as JSON: /api/errors/38128db3d488034e. Report an issue: GitHub.

Appendix: source

Thrown at crates/unicity-aos-bootstrap/src/lib.rs:669

        .ok_or_else(|| {
            io::Error::new(
                io::ErrorKind::InvalidData,
                "embedded distro has no capsules",
            )
        })?;
    let mut assets = Vec::with_capacity(capsules.len());
    for capsule in capsules {
        let package = capsule
            .get("name")
            .and_then(toml::Value::as_str)
            .ok_or_else(|| {
                io::Error::new(io::ErrorKind::InvalidData, "embedded capsule has no name")
            })?;
        let source = capsule
            .get("source")
            .and_then(toml::Value::as_str)
            .ok_or_else(|| {
                io::Error::new(io::ErrorKind::InvalidData, "embedded capsule has no source")
            })?;
        let relative = Path::new(source);
        let mut components = relative.components();
        if components.next() != Some(std::path::Component::Normal(OsStr::new("capsules")))
            || components
                .next()
                .and_then(|component| match component {
                    std::path::Component::Normal(name) => Some(name),
                    _ => None,
                })
                .is_none()
            || components.next().is_some()
            || relative.extension() != Some(OsStr::new("capsule"))
        {
            return Err(io::Error::new(
                io::ErrorKind::InvalidData,
                format!("embedded capsule source is not canonical: {source}"),
            ));

View on GitHub (pinned to f6f22024fb)