unicity-aos/aos-ce · warning · Finding

[subscribe] is empty or missing — the capsule cannot…

Error message

[subscribe] is empty or missing — the capsule cannot receive any event.

What it means

collect_topics also gathers keys from [subscribe] and warns when it is empty or missing. Subscription is fail-closed: without declared subscribe topics the capsule receives no events at all. Forge expects tool capsules to subscribe to one `tool.v1.execute.<tool>` topic per tool plus `tool.v1.request.describe`.

Solutions

  1. Add a [subscribe] section containing `tool.v1.execute.<tool>` for each tool the capsule implements plus `tool.v1.request.describe`.
  2. Check topics weren't accidentally declared under [publish]; move them to [subscribe].
  3. If the capsule is intentionally publish-only, accept the warning as documenting that no events are received.

Example fix

# before
[publish]
"tool.v1.execute.*.result" = {}
# after
[publish]
"tool.v1.execute.*.result" = {}

[subscribe]
"tool.v1.execute.echo" = {}
"tool.v1.request.describe" = {}
Defensive patterns

Strategy: validation

Validate before calling

# ensure [subscribe] exists with required keys before shipping
tomlq '.subscribe | keys' capsule.toml | grep -q 'tool.v1.execute' || echo "missing subscribe topics"

Prevention

When it happens

Trigger: validate_manifest -> collect_topics finds table_keys(root, "subscribe") empty — no [subscribe] section in the manifest, or it has zero topic keys.

Common situations: Fresh manifest skeleton without wiring yet; author renamed tools and removed subscriptions but never re-added them; copied a publish-only template; typo putting topics under [publish] instead of [subscribe].

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

                "Remove `scope`; shared/operator-only env scope is not honoured from the manifest.",
            ));
        }
    }
}

/// Collect the `[publish]` and `[subscribe]` topic keys, warning if a table is
/// empty (fail-closed: the capsule then can't publish/subscribe at all).
fn collect_topics(root: &Toml, out: &mut Vec<Finding>) -> (Vec<String>, Vec<String>) {
    let pub_keys = table_keys(root, "publish");
    let sub_keys = table_keys(root, "subscribe");
    if pub_keys.is_empty() {
        out.push(Finding::warn(
            "[publish] is empty or missing — the capsule cannot publish any event.",
            "Add the tool-bus publish keys `tool.v1.execute.*.result` and `tool.v1.response.describe.*`.",
        ));
    }
    if sub_keys.is_empty() {
        out.push(Finding::warn(
            "[subscribe] is empty or missing — the capsule cannot receive any event.",
            "Add one `tool.v1.execute.<tool>` per tool plus `tool.v1.request.describe`.",
        ));
    }
    (pub_keys, sub_keys)
}

fn table_keys(root: &Toml, table: &str) -> Vec<String> {
    root.get(table)
        .and_then(Toml::as_table)
        .map(|t| t.keys().cloned().collect())
        .unwrap_or_default()
}

/// Check the mandatory tool-bus wiring: each `tool.v1.execute.<x>` subscribe has
/// a handler, the two mandatory publish keys exist, and the describe request is
/// subscribed.
fn check_tool_bus(sub_keys: &[String], pub_keys: &[String], root: &Toml, out: &mut Vec<Finding>) {

View on GitHub (pinned to f6f22024fb)