{"record":{"id":"7bde57c583fa7789","repo":"astrid-runtime/astrid","slug":"invites-issuers-is-non-empty-but-invites-default-g","errorCode":null,"errorMessage":"invites.issuers is non-empty but invites.default-group is unset — either configure both or remove the [invites] section","messagePattern":"invites\\.issuers is non-empty but invites\\.default-group is unset — either configure both or remove the \\[invites\\] section","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/astrid-cli/src/commands/distro/validate.rs","lineNumber":218,"sourceCode":"    for cap in &manifest.capsules {\n        for (key, value) in &cap.env {\n            for var_ref in extract_variable_refs(value) {\n                if !defined_vars.contains(var_ref) {\n                    anyhow::bail!(\n                        \"capsule '{}' env.{key} references undefined variable '{{{{ {var_ref} }}}}'\",\n                        cap.name,\n                    );\n                }\n            }\n        }\n    }\n\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","sourceCodeStart":200,"sourceCodeEnd":236,"githubUrl":"https://github.com/astrid-runtime/astrid/blob/affd8760f44190dbdfbec23403f4c4b642c33112/crates/astrid-cli/src/commands/distro/validate.rs#L200-L236","documentation":"The [invites] block in a distro manifest must be coherent: if issuers is non-empty, default-group must also be set, because issued invites need a target group. validate_manifest enforces this fail-fast to catch typos before the kernel caps issuance at runtime.","triggerScenarios":"A manifest with `[invites]` containing one or more issuers but no `default-group` key. Detected during any distro manifest validation.","commonSituations":"Partially configured invite support — the author listed issuers then stopped before setting default-group; renamed/removed the default-group key by mistake; copied a partial [invites] snippet from docs.","solutions":["Set `default-group = \"<group>\"` under [invites].","Or remove the issuers entries if invites should not be issued.","Or delete the entire [invites] section if invite support is not wanted."],"exampleFix":"// before\n[invites]\nissuers = [\"alice\"]\n\n// after\n[invites]\nissuers = [\"alice\"]\ndefault-group = \"members\"","handlingStrategy":"validation","validationCode":"fn invites_coherent(invites: &Invites) -> Result<(), String> {\n    if !invites.issuers.is_empty() && invites.default_group.is_none() {\n        return Err(\"issuers set without default-group\".into());\n    }\n    Ok(())\n}","typeGuard":"fn invites_block_ok(i: &Invites) -> bool {\n    i.issuers.is_empty() || i.default_group.is_some()\n}","tryCatchPattern":"if let Some(invites) = &manifest.invites {\n    if let Err(e) = invites_coherent(invites) {\n        eprintln!(\"[invites]: {e}\");\n        std::process::exit(1);\n    }\n}","preventionTips":["Treat [invites] as all-or-nothing: configure issuers and default-group in the same change.","Copy the full [invites] example from docs, not fragments.","Run distro validation in CI so partial invite config fails fast."],"tags":["manifest","validation","invites","configuration"],"backgroundTag":"conflicting-config-options","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"}