unicity-aos/aos-ce · error

`capabilities` must be a TOML table.

Error message

`capabilities` must be a TOML table.

What it means

In `check_capabilities`, if a `capabilities` key exists in Capsule.toml but its value is not a TOML table (e.g. an array or scalar string), the linter emits this error and skips the per-key capability checks. The manifest schema requires capabilities to be a `[capabilities]` table whose keys are capability names.

Solutions

  1. Change the value to a table: `[capabilities]` with per-capability keys.
  2. Keep only the fields the capsule actually requires and remove extra data.
  3. Re-run the forge lints to confirm no other capability errors remain.

Example fix

// before
capabilities = ["uplink"]

// after
[capabilities]
uplink = ["net:read"]
Defensive patterns

Strategy: validation

Validate before calling

// Rust
if let Some(cap) = root.get("capabilities") {
    if !cap.is_table() { return Err("[capabilities] must be a TOML table"); }
}

Type guard

fn is_table(v: &toml::Value) -> bool { v.is_table() }

Prevention

When it happens

Trigger: Writing `capabilities = ["uplink"]` (array) or `capabilities = "x"` (string) instead of a table; the key is present but `as_table()` returns None.

Common situations: Copying a JSON-style list into the manifest, misunderstanding whether capabilities is a list of scopes versus a map, or converting older manifests where capabilities had a different shape.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at capsules/capsule-forge/src/checks.rs:86

    };

    let mut out = Vec::new();
    check_package(&root, &mut out);
    check_component(&root, &mut out);
    check_capabilities(&root, &mut out);
    check_env(&root, &mut out);
    let (pub_keys, sub_keys) = collect_topics(&root, &mut out);
    check_tool_bus(&sub_keys, &pub_keys, &root, &mut out);
    check_topic_shapes(&pub_keys, &sub_keys, &mut out);
    out
}

fn check_capabilities(root: &Toml, out: &mut Vec<Finding>) {
    let Some(capabilities) = root.get("capabilities") else {
        return;
    };
    let Some(capabilities) = capabilities.as_table() else {
        out.push(Finding::err(
            "`capabilities` must be a TOML table.",
            "Use a [capabilities] table with only the fields required by the capsule.",
        ));
        return;
    };
    const LIST_FIELDS: &[&str] = &[
        "net",
        "kv",
        "fs_read",
        "fs_write",
        "host_process",
        "net_bind",
        "net_connect",
        "identity",
    ];
    const BOOL_FIELDS: &[&str] = &["uplink", "allow_persistent", "allow_prompt_injection"];

    for (key, value) in capabilities {

View on GitHub (pinned to f6f22024fb)