{"record":{"id":"4f98dadc4fb57579","repo":"Hmbown/CodeWhale","slug":"fleet-task-metadata-coordination-contracts-mu","errorCode":null,"errorMessage":"fleet task '{}' metadata.coordination_contracts must be an array of strings","messagePattern":"fleet task '(.+?)' metadata\\.coordination_contracts must be an array of strings","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/worker_runtime.rs","lineNumber":383,"sourceCode":"                    path.display()\n                );\n            }\n            value => segments.push(value),\n        }\n    }\n    Ok(if segments.is_empty() {\n        \".\".to_string()\n    } else {\n        segments.join(\"/\")\n    })\n}\n\nfn fleet_coordination_contracts(task_spec: &FleetTaskSpec) -> Result<Vec<String>> {\n    let Some(value) = task_spec.metadata.get(\"coordination_contracts\") else {\n        return Ok(Vec::new());\n    };\n    let Some(values) = value.as_array() else {\n        bail!(\n            \"fleet task '{}' metadata.coordination_contracts must be an array of strings\",\n            task_spec.id\n        );\n    };\n    if values.len() > 16 {\n        bail!(\n            \"fleet task '{}' metadata.coordination_contracts accepts at most 16 entries\",\n            task_spec.id\n        );\n    }\n    let mut contracts = Vec::new();\n    for value in values {\n        let Some(value) = value.as_str() else {\n            bail!(\n                \"fleet task '{}' metadata.coordination_contracts must contain only strings\",\n                task_spec.id\n            );\n        };","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/fleet/worker_runtime.rs#L365-L401","documentation":"The task's `metadata.coordination_contracts` value exists but is not an array — `fleet_coordination_contracts` requires `serde_json::Value::Array`. In TOML task files, metadata fields deserialize into JSON values, so a bare string, number, or inline table fails this check. Coordination contracts are how write-capable tasks declare their coordination surface when they do not use writable_paths.","triggerScenarios":"`coordination_contracts = \"ledger\"` (plain string) or `= 3` or an inline table under `[metadata]`; a JSON spec with `\"coordination_contracts\": {\"name\": \"ledger\"}`.","commonSituations":"Authors writing a single contract as a scalar instead of a one-element list; generators emitting the wrong JSON type; upgrading an old spec format that allowed a string.","solutions":["Wrap the value in an array: `coordination_contracts = [\"ledger\"]` in TOML or `[\"ledger\"]` in JSON.","If the generator emits this field, fix it to always serialize a list of strings.","Remove the key entirely if the task uses writable_paths and needs no contracts."],"exampleFix":"# before\n[metadata]\ncoordination_contracts = \"ledger\"\n\n# after\n[metadata]\ncoordination_contracts = [\"ledger\"]","handlingStrategy":"validation","validationCode":"fn contracts_well_typed(metadata: &std::collections::BTreeMap<String, serde_json::Value>) -> bool {\n    match metadata.get(\"coordination_contracts\") {\n        None => true,\n        Some(serde_json::Value::Array(_)) => true,\n        Some(_) => false,\n    }\n}","typeGuard":"fn is_contract_array(value: &serde_json::Value) -> bool {\n    matches!(value, serde_json::Value::Array(_))\n}","tryCatchPattern":null,"preventionTips":["Serialize coordination_contracts from Vec<String> so the shape is always a JSON array.","In TOML, use bracket list syntax: coordination_contracts = [\"ledger\"].","Prefer omitting the key over writing a placeholder scalar."],"tags":["fleet","metadata","coordination","validation","rust"],"backgroundTag":"schema-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}