{"record":{"id":"ff5c9ca5a394c6b8","repo":"zeroclaw-labs/zeroclaw","slug":"google-workspace-allowed-operations-i-service","errorCode":null,"errorMessage":"google_workspace.allowed_operations[{i}].service '{service}' is not in the effective allowed_services; this entry can never match at runtime","messagePattern":"google_workspace\\.allowed_operations\\[(.+?)\\]\\.service '(.+?)' is not in the effective allowed_services; this entry can never match at runtime","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21853,"sourceCode":"        for (i, operation) in self.google_workspace.allowed_operations.iter().enumerate() {\n            let service = operation.service.trim();\n            let resource = operation.resource.trim();\n\n            if service.is_empty() {\n                validation_bail!(\n                    RequiredFieldEmpty,\n                    format!(\"google_workspace.allowed_operations[{i}].service\"),\n                    \"google_workspace.allowed_operations[{i}].service must not be empty\"\n                );\n            }\n            if resource.is_empty() {\n                anyhow::bail!(\n                    \"google_workspace.allowed_operations[{i}].resource must not be empty\"\n                );\n            }\n\n            if !effective_services.contains(service) {\n                anyhow::bail!(\n                    \"google_workspace.allowed_operations[{i}].service '{service}' is not in the \\\n                     effective allowed_services; this entry can never match at runtime\"\n                );\n            }\n            if !service\n                .chars()\n                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')\n            {\n                anyhow::bail!(\n                    \"google_workspace.allowed_operations[{i}].service contains invalid characters: {service}\"\n                );\n            }\n            // Unlike service IDs, resource/sub_resource/method names are camelCase\n            // in the Google APIs (calendarList, quickAdd, batchUpdate), so\n            // uppercase must be accepted here and in the runtime tool check.\n            if !resource\n                .chars()\n                .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')","sourceCodeStart":21835,"sourceCodeEnd":21871,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21835-L21871","documentation":"This is a cross-field consistency check inside Config::validate(): every allowed_operations entry's `service` must be contained in the effective allowed_services set, which is the explicit google_workspace.allowed_services list, or the built-in DEFAULT_GWS_SERVICES when that list is left empty (schema.rs:21823). Because the runtime tool gate consults allowed_services first, an operation naming an unlisted service could never match; validate() fails fast at load time rather than accepting a dead entry.","triggerScenarios":"(a) google_workspace.allowed_services = [\"calendar\"] together with an operation entry whose service = \"gmail\"; (b) allowed_services omitted (defaults apply) and an operation uses a service ID outside DEFAULT_GWS_SERVICES, e.g. a typo like \"gcal\".","commonSituations":"Adding an operation for a new Google service but forgetting to widen allowed_services; later trimming allowed_services while stale operation entries remain; typos in service IDs that still pass the character check.","solutions":["Add the operation's service ID to google_workspace.allowed_services (e.g. \"gmail\")","If the entry is stale, remove that [[google_workspace.allowed_operations]] block instead","Check the service ID spelling against the DEFAULT_GWS_SERVICES list / Google API naming (calendar, gmail, drive, ...)"],"exampleFix":"# before\n[google_workspace]\nallowed_services = [\"calendar\"]\n\n[[google_workspace.allowed_operations]]\nservice = \"gmail\"\nresource = \"messages\"\nmethods = [\"get\"]\n\n# after\n[google_workspace]\nallowed_services = [\"calendar\", \"gmail\"]\n\n[[google_workspace.allowed_operations]]\nservice = \"gmail\"\nresource = \"messages\"\nmethods = [\"get\"]","handlingStrategy":"validation","validationCode":"// Pre-flight: operation services must be a subset of the effective allowed set\nlet effective: std::collections::HashSet<&str> = if cfg.google_workspace.allowed_services.is_empty() {\n    DEFAULT_GWS_SERVICES.iter().copied().collect()\n} else {\n    cfg.google_workspace.allowed_services.iter().map(|s| s.trim()).collect()\n};\nfor op in &cfg.google_workspace.allowed_operations {\n    if !effective.contains(op.service.trim()) { /* reject before validate() */ }\n}","typeGuard":"fn op_service_allowed(service: &str, effective: &std::collections::HashSet<&str>) -> bool {\n    effective.contains(service.trim())\n}","tryCatchPattern":"match config.validate() {\n    Ok(()) => {}\n    Err(e) if e.to_string().contains(\"is not in the effective allowed_services\") => {\n        // reconcile allowed_services vs allowed_operations, then reload\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Define allowed_services once and derive operation entries from it in your config generator","Add a unit test asserting operation services are a subset of allowed_services","When trimming allowed_services, grep allowed_operations for removed services in the same change"],"tags":["config","google-workspace","cross-field-validation","toml"],"backgroundTag":"config-cross-field-mismatch","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}