unicity-aos/aos-ce · warning · Finding

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

Error message

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

What it means

collect_topics gathers keys from the manifest's [publish] table and warns when it is empty or absent. Publishing is fail-closed: a capsule with no declared publish topics cannot publish any event on the bus. Forge warns because the standard tool-bus capsules are expected to publish tool results and describe responses.

Solutions

  1. Add a [publish] section with the tool-bus keys `tool.v1.execute.*.result` and `tool.v1.response.describe.*`.
  2. If the capsule genuinely publishes nothing, acknowledge the warning is intentional and leave it (behavior is fail-closed by design).
  3. Re-run forge validate after adding topics to confirm the warning clears.

Example fix

# before
[subscribe]
"tool.v1.execute.echo" = {}
# after
[subscribe]
"tool.v1.execute.echo" = {}

[publish]
"tool.v1.execute.*.result" = {}
"tool.v1.response.describe.*" = {}
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: validate_manifest -> collect_topics finds table_keys(root, "publish") empty — either the [publish] section is missing from the manifest or it contains no topic keys.

Common situations: New capsule scaffolded with only a [subscribe] section; author deleting publish entries after refactoring; template manifest for a purely-subscribing capsule reused for a tool capsule that must reply on the tool bus.

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/8c576ba81b1a47b7. Report an issue: GitHub.

Appendix: source

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

        return;
    };
    for (key, val) in env {
        if val.get("scope").and_then(Toml::as_str) == Some("shared") {
            out.push(Finding::warn(
                format!("[env].{key} sets scope = \"shared\", which is silently ignored."),
                "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()

View on GitHub (pinned to f6f22024fb)