diff --git a/.github/workflows/contributor-card-bot.yml b/.github/workflows/contributor-card-bot.yml index a582f9fc6..42acd2a75 100644 --- a/.github/workflows/contributor-card-bot.yml +++ b/.github/workflows/contributor-card-bot.yml @@ -6,10 +6,8 @@ name: Contributor Card Bot # # Intentionally NOT included: pull_request_review, pull_request_review_comment, # issue_comment. GitHub withholds repository secrets from these events when -# they originate on forked PRs, which is precisely the path most external -# contributor activity takes; the bot requires BOT_APP_* secrets to authenticate, -# so wiring those events here would fail-closed exactly for the contributors we -# want to recognize. They can be re-added later via a workflow_run handoff. +# they originate on forked PRs, so the relay secret would fail-closed there too. +# They can be re-added later via a workflow_run handoff. on: pull_request_target: types: [closed] @@ -24,21 +22,16 @@ on: permissions: contents: read -# Serialize all bot runs across the whole repository. The bot reads-then-writes -# `data/contributor-card-state.json`; running events in parallel let multiple -# runs read the same SHA and only the first PUT succeeds, the rest fail with a -# 409 Conflict. They also let the same actor receive duplicate cards when a -# burst of events fires before the first state write lands. A single repo-wide -# group with `cancel-in-progress: false` queues runs and processes them in -# arrival order, so every event still gets a card and the state file is never -# stale on write. +# Serialize all relay runs across the whole repository. The Cloudflare worker +# owns durable state now, but queuing still keeps identical GitHub events from +# racing each other and producing duplicate comments during bursty merge windows. concurrency: group: contributor-card-bot cancel-in-progress: false jobs: recognize: - name: Render and post contributor card + name: Relay contributor event to Cloudflare worker if: | github.repository == 'nexu-io/open-design' && ( @@ -49,32 +42,46 @@ jobs: github.event_name == 'workflow_dispatch' ) runs-on: ubuntu-latest - timeout-minutes: 8 + timeout-minutes: 5 steps: - - name: Checkout contributor bot - uses: actions/checkout@v6.0.2 - with: - repository: nexu-io/open-design-bot-sandbox - ref: main - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: 10 - - - name: Setup Node.js - uses: actions/setup-node@v6.0.0 - with: - node-version: 22 - cache: pnpm - - - name: Install bot dependencies - run: pnpm install --frozen-lockfile - - - name: Run contributor bot + - name: Relay event payload env: - BOT_APP_ID: ${{ secrets.BOT_APP_ID }} - BOT_APP_INSTALLATION_ID: ${{ secrets.BOT_APP_INSTALLATION_ID }} - BOT_APP_PRIVATE_KEY: ${{ secrets.BOT_APP_PRIVATE_KEY }} - run: pnpm exec tsx scripts/action-handler.ts + CONTRIBUTOR_CARD_WORKER_URL: ${{ secrets.CONTRIBUTOR_CARD_WORKER_URL }} + CONTRIBUTOR_CARD_WORKER_SECRET: ${{ secrets.CONTRIBUTOR_CARD_WORKER_SECRET }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_EVENT_ACTION: ${{ github.event.action }} + GITHUB_RUN_ID: ${{ github.run_id }} + GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }} + GITHUB_EVENT_PATH: ${{ github.event_path }} + run: | + python - <<'PY' + import json, os + from pathlib import Path + + payload = json.loads(Path(os.environ["GITHUB_EVENT_PATH"]).read_text()) + envelope = { + "repository": os.environ["GITHUB_REPOSITORY"], + "eventName": os.environ["GITHUB_EVENT_NAME"], + "action": os.environ.get("GITHUB_EVENT_ACTION") or payload.get("action"), + "deliveryId": f"{os.environ['GITHUB_RUN_ID']}-{os.environ['GITHUB_RUN_ATTEMPT']}", + "triggeredAt": __import__("datetime").datetime.utcnow().isoformat(timespec="milliseconds") + "Z", + "payload": payload, + } + Path("body.json").write_text(json.dumps(envelope, separators=(",", ":"))) + PY + python - <<'PY' + import hashlib, hmac, os + from pathlib import Path + + body = Path("body.json").read_bytes() + digest = hmac.new(os.environ["CONTRIBUTOR_CARD_WORKER_SECRET"].encode(), body, hashlib.sha256).hexdigest() + Path("signature.txt").write_text(f"sha256={digest}") + PY + curl --fail --silent --show-error \ + -X POST \ + -H "content-type: application/json" \ + -H "x-open-design-signature: $(cat signature.txt)" \ + --data-binary @body.json \ + "$CONTRIBUTOR_CARD_WORKER_URL/api/github/events"