Add Discord role sync with role-centric architecture (#22)

* Add Discord role sync with role-centric architecture

This refactors the access management to use a role-centric model where
each role explicitly defines which platforms it exists on (GitHub,
Discord, Google).

Key changes:
- Add Discord role sync via Pulumi Dynamic Provider
- Replace groups.ts with roleIds.ts + roles.ts for type-safe role definitions
- Use ROLE_IDS constants to prevent typos in memberOf references
- Each role has platform-specific config (github/discord/google keys)
- Discord-only roles (e.g., administrators) and GitHub-only roles
  (e.g., typescript-sdk-auth) are now explicitly modeled
- Add PR preview workflow to show Pulumi changes before merge
- Add test-config.ts for config structure validation
- Fix typo: docs-maintaners -> docs-maintainers

Discord roles will match existing server roles:
- administrators, lead maintainers, core maintainers
- sdk maintainers, working groups, interest groups
- etc.

Requires new GitHub secrets:
- DISCORD_BOT_TOKEN
- DISCORD_GUILD_ID

* Fix GitHub team ordering and make Discord integration optional

- Add topological sort for GitHub teams to ensure parent teams are
  created before child teams, fixing the registry-wg parentTeamId issue
- Make Discord integration optional by using config.get() instead of
  config.require(), allowing CI preview to succeed without Discord secrets
- Add sortRolesByGitHubDependency() utility function in config/utils.ts

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=20%)
Claude-Steers: 2
Claude-Permission-Prompts: 1
Claude-Escapes: 0

* Reconcile Discord roles for listed users

Replace per-role-membership approach with per-user role sync that:
- Adds missing roles users should have
- Removes managed roles users shouldn't have
- Only touches roles defined in config (won't affect admin roles, etc.)

Users not in config remain untouched.

Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 4
Claude-Permission-Prompts: 1
Claude-Escapes: 0

* Fix descriptions to match existing resources

* Cache Pulumi plugins and fix GITHUB_TOKEN for pulumi install

* Remove accidentally committed state backup

* Use github.token for pulumi install

* Fix formatting

Fix deploy: add pulumi login before config set

Fix deploy: set PULUMI_CONFIG_PASSPHRASE_FILE for config commands

Import existing docs-maintainers team (remove after deploy)

Remove docs-maintainers import workaround

Add docs-maintainers cleanup to state cleanup

Add pulumi refresh before up to sync state with reality

Rename Discord roles to add (synced) suffix

Cleanup Makefile and add lead-maintainers GitHub team
This commit is contained in:
David Soria Parra 2026-01-13 22:03:59 +00:00 • committed by David Soria Parra
commit f960494174
No known key found for this signature in database

75
src/config/roleIds.ts Normal file
View file

@ -0,0 +1,75 @@
/**
* Role ID constants for type-safe role references.
* Using constants prevents typos and enables autocomplete.
*/
export const ROLE_IDS = {
// ===================
// Organization Structure
// ===================
STEERING_COMMITTEE: 'steering-committee',
CORE_MAINTAINERS: 'core-maintainers',
LEAD_MAINTAINERS: 'lead-maintainers',
MODERATORS: 'moderators',
ADMINISTRATORS: 'administrators', // Discord only
// ===================
// Maintainer Groups
// ===================
MAINTAINERS: 'maintainers',
DOCS_MAINTAINERS: 'docs-maintainers',
INSPECTOR_MAINTAINERS: 'inspector-maintainers',
MCPB_MAINTAINERS: 'mcpb-maintainers',
REFERENCE_SERVERS_MAINTAINERS: 'reference-servers-maintainers',
REGISTRY_MAINTAINERS: 'registry-maintainers',
USE_MCP_MAINTAINERS: 'use-mcp-maintainers',
// ===================
// SDK Maintainers
// ===================
SDK_MAINTAINERS: 'sdk-maintainers',
CSHARP_SDK: 'csharp-sdk',
GO_SDK: 'go-sdk',
JAVA_SDK: 'java-sdk',
KOTLIN_SDK: 'kotlin-sdk',
PHP_SDK: 'php-sdk',
PYTHON_SDK: 'python-sdk',
PYTHON_SDK_AUTH: 'python-sdk-auth', // GitHub only (CODEOWNERS)
RUBY_SDK: 'ruby-sdk',
RUST_SDK: 'rust-sdk',
SWIFT_SDK: 'swift-sdk',
TYPESCRIPT_SDK: 'typescript-sdk',
TYPESCRIPT_SDK_AUTH: 'typescript-sdk-auth', // GitHub only (CODEOWNERS)
// ===================
// Working Groups
// ===================
WORKING_GROUPS: 'working-groups',
AUTH_WG: 'auth-wg',
SECURITY_WG: 'security-wg',
SERVER_IDENTITY_WG: 'server-identity-wg',
TRANSPORT_WG: 'transport-wg',
// ===================
// Interest Groups
// ===================
INTEREST_GROUPS: 'interest-groups',
AGENTS_IG: 'agents-ig',
AUTH_IG: 'auth-ig',
CLIENT_IMPLEMENTOR_IG: 'client-implementor-ig',
FINANCIAL_SERVICES_IG: 'financial-services-ig',
// ===================
// Email Groups (Google only)
// ===================
ANTITRUST: 'antitrust',
CATCH_ALL: 'catch-all',
} as const;
export type RoleId = (typeof ROLE_IDS)[keyof typeof ROLE_IDS];
/**
* Helper to check if a string is a valid RoleId at runtime.
*/
export function isValidRoleId(id: string): id is RoleId {
return Object.values(ROLE_IDS).includes(id as RoleId);
}