e9e9f1b added a one-off 'pulumi state delete' to the Deploy to Production
step to drop the stale RepositoryCollaborators resource for the renamed
experimental-ext-skills repo, with a note to remove it after one green deploy.
Deploys have been green since run #257 (2026-09-10), so the URN no longer
exists in state and the command is a no-op guarded by '|| true'. Remove it so
the deploy step runs 'make up' as before.
Claude-Session: https://claude.ai/code/session_01H4JKj5FVkmj6bRzgh1GPrY
Co-authored-by: Claude <noreply@anthropic.com>
Deploys have failed since #178 because experimental-ext-skills was renamed
to ext-skills on GitHub and the stale RepositoryCollaborators state 404s on
update. Point config at the new name and drop the old resource from state
before 'make up' (same approach as #126; remove after one green deploy).
#168 also referenced SKILLS_OVER_MCP_IG, which #170 had renamed to
SKILLS_OVER_MCP_WG, and left tobi-oye out of sort order, breaking tsc and
'npm run check'.
* Add npm/PyPI package publishing access management (config + drift detection)
Declare expected npm org membership, per-package maintainers and trusted
publishers, and PyPI project rosters in src/config/packageAccess.ts, with
npm/pypi identity fields on members in users.ts.
Deliberately outside the Pulumi resource graph: npm governance writes
require an interactive 2FA challenge since August 2026 (read endpoints
still work with a granular access token), and PyPI has no management API
at all. Drift against the live npm registry is detected read-only by
scripts/check-package-drift.ts (weekly via package-drift.yml, no-op when
the NPM_READ_TOKEN secret is absent), which emits a remediation plan of
npm CLI commands for a maintainer to run in one 2FA-approved session.
PyPI procedures are documented as a manual runbook in the README.
* Encode all slashes when escaping package names in registry URLs
Fixes the CodeQL finding: String.replace with a string pattern only
replaces the first occurrence. Use a shared helper with a global regex,
following the npm CLI convention (keep '@' literal, encode every '/').
---------
Co-authored-by: Claude <noreply@anthropic.com>
Dependabot-triggered runs read from the Dependabot secrets store, which
is empty, so the Google Cloud auth step receives an empty credential and
fails. Skip the preview job for Dependabot; CI still validates those PRs.
Also guard the PR comment step so it doesn't post 'Failed to read
preview output' when the preview step never ran.
Co-authored-by: Claude <noreply@anthropic.com>
The "Verify GroupMember state healed (TEMP, #133)" step passed on deploy
run 28624873606 (attempt 4), meeting the documented removal criterion for
the one-time state surgery added in #139. Also removes the stale TEMP
pulumi state delete for experimental-ext-tasks (#125), whose
one-successful-deploy condition was met on 6/30.
Co-authored-by: Claude <noreply@anthropic.com>
* Repair corrupted GroupMember state so deploys stop failing (#133)
Every Deploy to Production run since 2026-06-30 aborts about a minute
in with "googleapi: Error 409: Member already exists" and merged
changes (e.g. #134, #137) never apply. Two external-email GroupMember
records in the maintainers group are recorded in state with an empty
member ID ("groups/04f1mdlm38smb30/members/"), so each pulumi up plans
a create-before-delete replace whose create conflicts with the live
membership and fails the whole update. Five other records corrupted at
the same time were already healed by the 2026-07-02 deploy run.
Add a one-time TEMP block to the deploy workflow (same precedent as the
auth-maintainers repair and the experimental-ext-tasks state delete):
drop the two broken records, then re-import each membership by member
email so the provider records the numeric member ID. All commands are
|| true-guarded, and the block must be removed after one successful
deploy.
Fixes#133
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NazA3QKftRn72tdPG5vrpb
* Widen state repair to all seven corrupted GroupMember records (#133)
* Replace per-URN state deletes with export surgery; verify healing post-deploy (#133)
The committed repair used 'pulumi state delete <urn>' per record, but the
failed create-before-delete replaces left duplicate same-URN entries (a
live record plus a pending-delete leftover) for some of the seven
corrupted GroupMember records. 'pulumi state delete' fails on an
ambiguous URN, and the '|| true' guard hid that failure, making the
repair a silent no-op.
- Replace the per-URN deletes with state surgery: export the stack,
drop every entry (including pending-delete duplicates) whose URN is
one of the seven with jq, and re-import. Unguarded, so a surgery
failure is loud; skips the import when nothing matches (idempotent).
- Keep the seven imports-by-email ('|| true' so a not-currently-live
membership falls back to a create instead of blocking the deploy).
- Add a post-deploy verification step that fails if any GroupMember
record still has an empty member ID or a duplicate URN. The pinned
provider (SamuZad/googleworkspace 0.11.1) writes an empty member ID
even on successful creates (fixed upstream in 0.11.2), so a green
'make up' alone does not prove healing; the TEMP block must only be
removed after this verification step passes.
- Correct the root-cause comment: Google-side membership removals ->
refresh dropped the records -> re-create -> provider create bug wrote
empty IDs.
* Bump googleworkspace provider 0.11.1 -> 0.11.2 (#133)
0.11.2 carries upstream commit "handle edge case with group
memberships" (SamuZad/terraform-provider-googleworkspace, 2026-03-12),
which fixes the create-path bug that stores an empty member ID for
external-email group members — the bug that corrupted the seven records
repaired by the TEMP block in deploy.yml and that would re-corrupt any
record restored via the fallback create path. CI regenerates the SDK
from this pin via 'pulumi install' and typechecks against it.
---------
Co-authored-by: Claude <noreply@anthropic.com>
The repo was renamed to ext-tasks in #125, but Pulumi state still holds
the old RepositoryCollaborators resource. The first 'pulumi up' (which
runs before refresh) tries to delete it and 404s, blocking all deploys.
Drop it from state explicitly before 'make up'. Idempotent via '|| true';
remove this line after one successful deploy.
* Manage GitHub org settings and org-role assignments via Pulumi
Brings two click-ops org-level configs under IaC and tightens defaults:
- OrganizationSettings: default_repository_permission read -> none so
private repos are not implicitly readable by every member; repo
creation restricted to admins; billingEmail from Pulumi secret;
security new-repo defaults pinned so the provider does not reset them.
- Org-role assignments: codify lead/core-maintainers -> all_repo_admin.
moderators -> all_repo_triage and anthropic -> all_repo_write are
intentionally omitted (being removed).
* Fix config namespace: use project namespace for githubBillingEmail
The github: namespace is reserved for the @pulumi/github provider's own
config keys, so github:billingEmail was rejected as an unknown provider
option. Use the project default namespace instead. preview.yml uses the
same --config flag pattern as the existing Discord secrets.
* Import auth-maintainers team into Pulumi state with correct ID
The auth-wg -> auth-maintainers rename (PR #50) caused the team to be
lost from Pulumi state. A previous import attempt used the wrong team
ID (14435880). The actual GitHub team ID is 16083315.
Every deploy since has failed with:
422 Validation Failed: Name must be unique for this org
This adds a one-time import step to reconcile state. The import line
should be removed after the next successful deploy.
* Remove one-time auth-maintainers import from deploy workflow
The import in the previous commit successfully reconciled the
auth-maintainers team (ID 16083315) into Pulumi state. This
removes the one-time import line now that state is consistent.
The auth-wg -> auth-maintainers rename (PR #50) caused the team to be
lost from Pulumi state. A previous import attempt used the wrong team
ID (14435880). The actual GitHub team ID is 16083315.
Every deploy since has failed with:
422 Validation Failed: Name must be unique for this org
This adds a one-time import step to reconcile state. The import line
should be removed after the next successful deploy.
The team was accidentally deleted when both auth-wg and auth-maintainers
existed in state pointing to the same GitHub team. Need to clear stale
state so Pulumi can create fresh resources.
Fork PRs don't have access to repository secrets, causing the GCP
auth and PR comment steps to fail. Instead of failing, skip the
preview job entirely for fork PRs. The job will show as 'skipped'
rather than 'failed', allowing fork PRs to be merged based on
the CI check alone.
Claude-Generated-By: Claude Code (cli/claude-opus-4-5=100%)
Claude-Steers: 2
Claude-Permission-Prompts: 1
Claude-Escapes: 0
* 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
Pass the token via GITHUB_TOKEN environment variable which the Pulumi
GitHub provider reads automatically. Remove the one-time migration
workflow since state cleanup already succeeded.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add migrate-state.yml workflow (manual trigger) that:
- Sets github:token in Pulumi config from PULUMI_GITHUB_TOKEN secret
- Cleans up orphaned 'core' team memberships from state
- Commits the updated Pulumi.prod.yaml back to repo
- Revert deploy.yml to use encrypted token from Pulumi config
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Move GitHub token from Pulumi encrypted config to GitHub Actions secret
(PULUMI_GITHUB_TOKEN) for better secret management. The token is passed
via GITHUB_TOKEN env var which the Pulumi GitHub provider reads automatically.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add prettier with consistent formatting config
- Add format and format:check npm scripts
- Add prettier pre-commit hook
- Add validation and formatting checks to CI workflow
- Reformat all TypeScript and JSON files
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addresses security concern where secrets were directly interpolated in
run commands. Following GitHub's security best practices, the secret is
now passed through an intermediate environment variable before being
written to the file.
This maintains compatibility with the existing Makefile workflow while
reducing the risk of accidental secret disclosure.