unicity-aos/aos-ce · error

Mandatory publish key

Error message

Mandatory publish key `{required}` is missing.

What it means

When a manifest declares it executes tools (`saw_execute_tool`), the tool-bus protocol requires the capsule to publish results and describe responses on two mandatory topics: `tool.v1.execute.*.result` and `tool.v1.response.describe.*`. check_tool_bus scans the [publish] keys and fails the manifest if either is missing, because downstream consumers of tool results/describe would break.

Solutions

  1. Add `"tool.v1.execute.*.result" = { wit = ... }` to the [publish] table.
  2. Add `"tool.v1.response.describe.*" = { wit = ... }` to the [publish] table.
  3. Re-run `capsule-forge validate` and check the sibling error for `tool.v1.request.describe` in [subscribe].

Example fix

// before ([publish])
"tool.v1.request.execute" = { wit = ... }
// after
"tool.v1.request.execute" = { wit = ... }
"tool.v1.execute.*.result" = { wit = ... }
"tool.v1.response.describe.*" = { wit = ... }
Defensive patterns

Strategy: validation

Validate before calling

// Check mandatory publish keys before validating
const REQUIRED: [&str; 2] = ["tool.v1.execute.*.result", "tool.v1.response.describe.*"];
for r in REQUIRED { assert!(publish.contains_key(r), "missing {r}"); }

Prevention

When it happens

Trigger: validate_manifest -> check_tool_bus with an execute-tool capability present but [publish] lacking `tool.v1.execute.*.result` or `tool.v1.response.describe.*`.

Common situations: Adding tool execution to a capsule without wiring result topics, trimming [publish] entries during refactoring, or copying a minimal manifest template that only covers requests.

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

Appendix: source

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

        }
        saw_execute_tool = true;
        let has_handler = sub_table
            .and_then(|t| t.get(key))
            .and_then(|v| v.get("handler"))
            .and_then(Toml::as_str)
            .is_some_and(|h| !h.is_empty());
        if !has_handler {
            out.push(Finding::err(
                format!("Subscribe `{key}` has no `handler`."),
                format!("Add `handler = \"tool_execute_{tool}\"` to the `{key}` row."),
            ));
        }
    }

    if saw_execute_tool {
        for required in ["tool.v1.execute.*.result", "tool.v1.response.describe.*"] {
            if !pub_keys.iter().any(|k| k == required) {
                out.push(Finding::err(
                    format!("Mandatory publish key `{required}` is missing."),
                    format!("Add `\"{required}\" = {{ wit = ... }}` to [publish]; tool results/describe break without it."),
                ));
            }
        }
        if !sub_keys.iter().any(|k| k == "tool.v1.request.describe") {
            out.push(Finding::err(
                "Subscribe `tool.v1.request.describe` is missing — tools won't be discoverable.",
                "Add `\"tool.v1.request.describe\" = { wit = ..., handler = \"tool_describe\" }` to [subscribe].",
            ));
        }
    }
}

/// Flag malformed topic keys: empty segments, or absurd depth (>8 segments).
fn check_topic_shapes(pub_keys: &[String], sub_keys: &[String], out: &mut Vec<Finding>) {
    for key in pub_keys.iter().chain(sub_keys) {
        if has_empty_segments(key) {

View on GitHub (pinned to f6f22024fb)