{"record":{"id":"08830bc8b6a04c4a","repo":"zeroclaw-labs/zeroclaw","slug":"google-workspace-allowed-services-i-contains-in","errorCode":null,"errorMessage":"google_workspace.allowed_services[{i}] contains invalid characters: {normalized}","messagePattern":"google_workspace\\.allowed_services\\[(.+?)\\] contains invalid characters: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-config/src/schema.rs","lineNumber":21808,"sourceCode":"            }\n        }\n\n        // Google Workspace allowed_services validation\n        let mut seen_gws_services = std::collections::HashSet::new();\n        for (i, service) in self.google_workspace.allowed_services.iter().enumerate() {\n            let normalized = service.trim();\n            if normalized.is_empty() {\n                validation_bail!(\n                    RequiredFieldEmpty,\n                    format!(\"google_workspace.allowed_services[{i}]\"),\n                    \"google_workspace.allowed_services[{i}] must not be empty\"\n                );\n            }\n            if !normalized\n                .chars()\n                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')\n            {\n                anyhow::bail!(\n                    \"google_workspace.allowed_services[{i}] contains invalid characters: {normalized}\"\n                );\n            }\n            if !seen_gws_services.insert(normalized.to_string()) {\n                anyhow::bail!(\n                    \"google_workspace.allowed_services contains duplicate entry: {normalized}\"\n                );\n            }\n        }\n\n        // Build the effective allowed-services set for cross-validation.\n        // When the operator leaves allowed_services empty the tool falls back to\n        // DEFAULT_GWS_SERVICES; use the same constant here so validation is\n        // consistent in both cases.\n        let effective_services: std::collections::HashSet<&str> =\n            if self.google_workspace.allowed_services.is_empty() {\n                DEFAULT_GWS_SERVICES.iter().copied().collect()\n            } else {","sourceCodeStart":21790,"sourceCodeEnd":21826,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-config/src/schema.rs#L21790-L21826","documentation":"Each entry in [google_workspace] allowed_services is normalized, then must consist solely of ASCII lowercase letters, digits, '_', or '-'. Unlike the OTP gated-actions check (which allows uppercase), this allowlist is strictly lowercase, so any uppercase letter, dot, space, or other punctuation fails with the offending value and index. The charset check runs before the duplicate check, so fix characters first.","triggerScenarios":"Set allowed_services to entries like \"Calendar\" (uppercase), \"drive.google.com\" (dots), \"sheets v4\" (space), \"Admin_Directory\" (uppercase), or any non-slug string.","commonSituations":"Copying API names from Google's API directory where they appear as CamelCase product names or dotted Discovery names (`admin.directory`); mixing product names with service slugs; hand-editing without a reference list.","solutions":["Slugify the entry: lowercase, dots/underscores to '-', or '_' — \"Calendar API\" -> \"calendar\", \"admin.directory\" -> \"admin_directory\"","Stick to the documented lowercase slug list for allowed services","Re-run validation after each fix — the loop bails on the first invalid entry"],"exampleFix":"# before\n[google_workspace]\nallowed_services = [\"Calendar\", \"drive.google.com\"]\n\n# after\n[google_workspace]\nallowed_services = [\"calendar\", \"drive\"]","handlingStrategy":"validation","validationCode":"fn valid_gws_service(name: &str) -> bool {\n    name.chars().all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-')\n}\n\nfn gws_services_precheck(services: &[String]) -> Result<(), String> {\n    if let Some(bad) = services.iter().find(|s| !valid_gws_service(s)) {\n        return Err(format!(\"invalid allowed_services entry: {bad:?}\"));\n    }\n    Ok(())\n}","typeGuard":"fn is_gws_service_slug(s: &str) -> bool {\n    !s.is_empty() && valid_gws_service(s)\n}","tryCatchPattern":"if let Err(err) = config.validate() {\n    if err.to_string().contains(\"google_workspace.allowed_services\") && err.to_string().contains(\"invalid characters\") {\n        // lowercase and slugify the entry at the reported index\n    }\n}","preventionTips":["Translate Google API names to lowercase slugs at authoring time (Calendar API -> calendar)","Lint allowed_services with the lowercase-only regex ^[a-z0-9_-]+$ before deploy","Keep a canonical slug list next to the config for reviewers"],"tags":["configuration","validation","google-workspace","zeroclaw","rust"],"backgroundTag":"invalid-identifier-characters","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}