{"record":{"id":"405a12aa52482970","repo":"unicity-aos/aos-ce","slug":"capabilities-must-be-a-toml-table","errorCode":null,"errorMessage":"`capabilities` must be a TOML table.","messagePattern":"`capabilities` must be a TOML table\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"capsules/capsule-forge/src/checks.rs","lineNumber":86,"sourceCode":"    };\n\n    let mut out = Vec::new();\n    check_package(&root, &mut out);\n    check_component(&root, &mut out);\n    check_capabilities(&root, &mut out);\n    check_env(&root, &mut out);\n    let (pub_keys, sub_keys) = collect_topics(&root, &mut out);\n    check_tool_bus(&sub_keys, &pub_keys, &root, &mut out);\n    check_topic_shapes(&pub_keys, &sub_keys, &mut out);\n    out\n}\n\nfn check_capabilities(root: &Toml, out: &mut Vec<Finding>) {\n    let Some(capabilities) = root.get(\"capabilities\") else {\n        return;\n    };\n    let Some(capabilities) = capabilities.as_table() else {\n        out.push(Finding::err(\n            \"`capabilities` must be a TOML table.\",\n            \"Use a [capabilities] table with only the fields required by the capsule.\",\n        ));\n        return;\n    };\n    const LIST_FIELDS: &[&str] = &[\n        \"net\",\n        \"kv\",\n        \"fs_read\",\n        \"fs_write\",\n        \"host_process\",\n        \"net_bind\",\n        \"net_connect\",\n        \"identity\",\n    ];\n    const BOOL_FIELDS: &[&str] = &[\"uplink\", \"allow_persistent\", \"allow_prompt_injection\"];\n\n    for (key, value) in capabilities {","sourceCodeStart":68,"sourceCodeEnd":104,"githubUrl":"https://github.com/unicity-aos/aos-ce/blob/f6f22024fb1e8d122f28a1b4a9f75aee448ae839/capsules/capsule-forge/src/checks.rs#L68-L104","documentation":"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.","triggerScenarios":"Writing `capabilities = [\"uplink\"]` (array) or `capabilities = \"x\"` (string) instead of a table; the key is present but `as_table()` returns None.","commonSituations":"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.","solutions":["Change the value to a table: `[capabilities]` with per-capability keys.","Keep only the fields the capsule actually requires and remove extra data.","Re-run the forge lints to confirm no other capability errors remain."],"exampleFix":"// before\ncapabilities = [\"uplink\"]\n\n// after\n[capabilities]\nuplink = [\"net:read\"]","handlingStrategy":"validation","validationCode":"// Rust\nif let Some(cap) = root.get(\"capabilities\") {\n    if !cap.is_table() { return Err(\"[capabilities] must be a TOML table\"); }\n}","typeGuard":"fn is_table(v: &toml::Value) -> bool { v.is_table() }","tryCatchPattern":null,"preventionTips":["Model capabilities as a map, never a list of scopes at the top level.","Start from the official capsule manifest template.","Run the forge lint before committing manifest changes."],"tags":["toml","schema-validation","config","lint"],"backgroundTag":"schema-validation-failed","analyzedSha":"f6f22024fb1e8d122f28a1b4a9f75aee448ae839","analyzedAt":"2026-09-13T03:04:44.565Z","contentChangedAt":"2026-09-13T03:04:44.565Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}