Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 53 additions & 19 deletions .github/workflows/publish-catalog.yml
Original file line number Diff line number Diff line change
@@ -1,43 +1,77 @@
name: Publish bot catalog

on:
pull_request:
types: [closed]
push:
branches: [main]
paths:
- 'bots/**'
workflow_dispatch:
inputs:
pull_request_number:
description: Merged bot submission PR to republish
required: true
type: number

permissions:
contents: write
pull-requests: read
checks: read

concurrency:
group: publish-catalog
cancel-in-progress: false

jobs:
publish:
if: github.event.pull_request.merged && github.event.pull_request.base.ref == 'main'
runs-on: ubuntu-latest
steps:
- name: Resolve and verify merged submission
id: submission
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
INPUT_PR_NUMBER: ${{ inputs.pull_request_number }}
run: |
set -euo pipefail
if [[ "$EVENT_NAME" == "workflow_dispatch" ]]; then
[[ "$GITHUB_REF" == "refs/heads/main" ]] || { echo "Dispatch this publisher from main." >&2; exit 1; }
pr_number="$INPUT_PR_NUMBER"
else
prs="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls")"
pr_number="$(jq -r '[.[] | select(.merged_at != null and .base.ref == "main")] | if length == 1 then .[0].number else empty end' <<< "$prs")"
fi
[[ "$pr_number" =~ ^[0-9]+$ ]] || { echo "Could not identify exactly one merged bot submission pull request." >&2; exit 1; }
pr="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${pr_number}")"
jq -e '.merged_at != null and .base.ref == "main"' <<< "$pr" >/dev/null || { echo "The selected pull request must be merged into main." >&2; exit 1; }
head_sha="$(jq -r '.head.sha' <<< "$pr")"
owner="$(jq -r '.user.login' <<< "$pr")"
[[ "$head_sha" =~ ^[0-9a-f]{40}$ && "$owner" =~ ^[A-Za-z0-9-]+$ ]] || { echo "Unexpected pull request identity." >&2; exit 1; }
successful_validation="$(gh api "repos/${GITHUB_REPOSITORY}/commits/${head_sha}/check-runs" --jq '[.check_runs[] | select(.name == "validate" and .app.slug == "github-actions" and .conclusion == "success")] | length')"
[[ "$successful_validation" -gt 0 ]] || { echo "The merged pull request has no successful validate check for its head commit." >&2; exit 1; }
{
echo "pull_request_number=$pr_number"
echo "head_sha=$head_sha"
echo "owner=$owner"
} >> "$GITHUB_OUTPUT"

- uses: actions/checkout@v4
with:
ref: main
- uses: actions/setup-python@v5
with:
python-version: '3.12'
# Java bots run from source (Java 22+ multi-file launch); match the Rumble's JVM.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '25'
- uses: actions/setup-dotnet@v4
with:
dotnet-version: '10.0.x'
- uses: actions/setup-node@v4
with:
node-version: '24'
- run: python -m pip install -r templates/python/StarterBot/requirements.txt
- run: python scripts/validate_bot.py --root . --owner '${{ github.event.pull_request.user.login }}' --smoke --generate
- run: |
# The separate pull_request validation ran bot smoke without write access. This publisher
# runs only on trusted main and performs static catalog generation; it never launches bot code.
- name: Regenerate catalog without running bots
env:
BOT_OWNER: ${{ steps.submission.outputs.owner }}
run: python scripts/validate_bot.py --root . --owner "$BOT_OWNER" --generate
- name: Commit and publish generated catalog
run: |
git config user.name 'github-actions[bot]'
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git add bots/index.json bots/owners.json
git diff --cached --quiet || git commit -m 'chore: regenerate bot catalog'
git push
if ! git diff --cached --quiet; then
git commit -m 'chore: regenerate bot catalog'
git push origin HEAD:main
fi
1 change: 1 addition & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ on:
- templates/**
- tests/**
- .github/workflows/validate.yml
- .github/workflows/publish-catalog.yml

permissions:
contents: read
Expand Down
10 changes: 10 additions & 0 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ Moderators maintain `bots/banned.json` through reviewed pull requests. A ban can
Moderators resolve name-squatting, lost-account recovery, license complaints, and appeal requests case by case. A credible copyright complaint removes the bot from the working tree and disqualifies it pending resolution.

Once each quarter, a moderator performs a fork drill: fork the repository, enable Actions, run validation, and regenerate the catalog without credentials other than the forge-provided token. Record the outcome in a GitHub issue.

## GitHub Actions permissions

The pull-request validation workflow requests `contents: read`. Public fork pull requests receive a read-only `GITHUB_TOKEN` even if a workflow asks for write access, and GitHub may require a maintainer with write permission to approve a first-time contributor's workflow run. Approve the validation run only after checking the proposed source; never grant the validation workflow write access to the upstream repository.

After a bot pull request is merged, the publisher runs on a push to `main` that changes `bots/**`. It uses the associated merged pull request to check that the submission's `validate` check passed, then checks out trusted `main` and runs static catalog generation without `--smoke`. The publisher must never execute a submitted bot launcher with its write token. Its `GITHUB_TOKEN` needs `contents: write`, `pull-requests: read`, and `checks: read`, as declared in `.github/workflows/publish-catalog.yml`; it uses no personal access token or repository secret.

For publication to work, GitHub Actions must be enabled and the repository or organization policy must permit these token scopes. The repository's **Settings → Actions → General → Workflow permissions** should allow read and write permissions for `GITHUB_TOKEN` (currently enabled for this repository); the workflow itself still grants only the scopes listed above. If `main` is protected by branch rules, allow `github-actions[bot]` to push the generated catalog files, or publishing will fail. Recheck these settings if organization policy or branch rules change.

GitHub may hold a fork contributor's validation run for approval by a maintainer with write access. The publisher does not need `pull_request_target` or a privileged fork workflow. To recover a merged submission whose publication failed, a maintainer can run **Publish bot catalog** from the `main` branch with its merged pull-request number. The dispatch verifies that it merged into `main` and that its head commit has a successful `validate` check before it regenerates the catalog using the PR author's account as owner.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ Read [CONTRIBUTING.md](CONTRIBUTING.md) for the full submission, ownership, lice

`bots/index.json` and `bots/owners.json` are generated by CI and must never be edited in a pull request. A catalog entry contains the bot identity, owner, source path, source-tree SHA-256, active lifecycle status, and `teamMembers`. Individual bots publish an empty member list; a TwinDuel team publishes exactly two active `<name> <version>` member identities, so a member release always forces the team entry to be resubmitted. A team directory contains nothing but its own JSON, its members may live under different platform directories, and its published platform is the members' shared platform or `Mixed`. Only `active` entries are eligible for matchmaking.

This repository is designed to be forkable: its validator is standard-library Python and GitHub Actions only invokes that script. The only forge seam is the workflow that supplies the pull-request author to the validator and publishes generated files after merge.
This repository is designed to be forkable: its validator is standard-library Python. Pull-request validation runs bot smoke checks with read-only permissions; after merge, a trusted `main` publisher verifies the merged pull request passed validation and regenerates catalog data without launching bot code. See [moderator governance](GOVERNANCE.md#github-actions-permissions) for required Actions permissions and recovery.
Source-only catalog for ranked Tank Royale Rumble bots
Loading