{"record":{"id":"62bce9a0f78c6059","repo":"astrid-runtime/astrid","slug":"invites-max-principals-must-be-unlimited-or-a-no","errorCode":null,"errorMessage":"invites.max-principals must be \"unlimited\" or a non-negative integer (got {cap:?})","messagePattern":"invites\\.max-principals must be \"unlimited\" or a non-negative integer \\(got (.+?)\\)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":230,"sourceCode":"\n    // Invite policy — additive, so the rule is \"if any field is set,\n    // the shape must be coherent\". The kernel still cap-gates issuance\n    // at runtime; this is fail-fast for typos.\n    if let Some(invites) = &manifest.invites {\n        if !invites.issuers.is_empty() && invites.default_group.is_none() {\n            anyhow::bail!(\n                \"invites.issuers is non-empty but invites.default-group is unset — \\\n                 either configure both or remove the [invites] section\"\n            );\n        }\n        if let Some(exp) = &invites.default_expires {\n            parse_invite_duration(exp).map_err(|e| anyhow::anyhow!(e))?;\n        }\n        if let Some(cap) = &invites.max_principals\n            && cap != \"unlimited\"\n            && cap.parse::<u32>().is_err()\n        {\n            anyhow::bail!(\n                \"invites.max-principals must be \\\"unlimited\\\" or a non-negative integer (got {cap:?})\",\n            );\n        }\n    }\n\n    // Branding — only structural rails. The dashboard interprets the\n    // values; the parser just refuses obvious garbage.\n    if let Some(branding) = &manifest.branding {\n        if let Some(icon) = &branding.icon\n            && icon.len() > 64 * 1024\n        {\n            anyhow::bail!(\n                \"branding.icon is {} bytes — distros must not embed assets larger than 64 KiB\",\n                icon.len()\n            );\n        }\n        if let Some(color) = &branding.primary_color {\n            validate_hex_color(color)","sourceCodeStart":212,"sourceCodeEnd":248,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L212-L248","documentation":"invites.max-principals, when set, must be either the literal string \"unlimited\" or a parseable non-negative integer (u32). Any other string — negative numbers, floats, text — is rejected by validate_manifest.","triggerScenarios":"A manifest where invites.max-principals is set to a value like \"-1\", \"10.5\", \"none\", or other non-numeric/non-\"unlimited\" text. Detected during any distro manifest validation.","commonSituations":"Typing \"none\" or \"null\" expecting unlimited semantics; writing a negative cap; adding a unit suffix like \"50 users\"; locale-formatted numbers with separators.","solutions":["Set max-principals to a non-negative integer string, e.g. \"100\".","Or set it to \"unlimited\" if no cap is desired.","Remove the key entirely if the cap is managed elsewhere."],"exampleFix":"// before\n[invites]\nmax-principals = \"none\"\n\n// after\n[invites]\nmax-principals = \"unlimited\"","handlingStrategy":"validation","validationCode":"fn valid_max_principals(cap: &Option<String>) -> bool {\n    match cap {\n        None => true,\n        Some(c) => c == \"unlimited\" || c.parse::<u32>().is_ok(),\n    }\n}","typeGuard":"fn parse_max_principals(cap: &str) -> Option<MaxPrincipals> {\n    if cap == \"unlimited\" { Some(MaxPrincipals::Unlimited) }\n    else { cap.parse::<u32>().ok().map(MaxPrincipals::Limited) }\n}","tryCatchPattern":"match parse_max_principals(cap_str) {\n    Some(_) => apply(),\n    None => eprintln!(\"max-principals must be \\\"unlimited\\\" or a u32, got {cap_str:?}\"),\n}","preventionTips":["Use \"unlimited\" verbatim — never \"none\"/\"null\"/\"inf\".","Store the cap as an integer in tooling that generates manifests.","Validate the whole [invites] block before committing."],"tags":["manifest","validation","invites","configuration"],"backgroundTag":"invalid-config-value","analyzedSha":"affd8760f44190dbdfbec23403f4c4b642c33112","analyzedAt":"2026-09-09T21:28:12.402Z","contentChangedAt":"2026-09-09T21:28:12.402Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}